@dreb/dashboard 2.35.0 → 2.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +146 -36
- package/dist/index.js.map +1 -1
- package/dist/static/assets/{index-Cgwg2zRA.js → index-BfEMioIL.js} +3 -3
- package/dist/static/assets/index-DN6kUWdQ.css +1 -0
- package/dist/static/index.html +2 -2
- package/dist/static/manifest.webmanifest +1 -1
- package/dist/static/sw.js +91 -28
- package/package.json +1 -1
- package/dist/static/assets/index-DnFrQoI9.css +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* Usage:
|
|
11
11
|
* dreb-dashboard [--port 5343] [--remote --allow me@example.com [--allow ...]]
|
|
12
12
|
*/
|
|
13
|
+
import { watch } from "node:fs";
|
|
13
14
|
export { DashboardAuth, TailscaleStatusResolver } from "./server/auth.js";
|
|
14
15
|
export { EventHub } from "./server/event-hub.js";
|
|
15
16
|
export { canonicalizePath, FileApi } from "./server/files.js";
|
|
@@ -27,4 +28,56 @@ interface CliArgs {
|
|
|
27
28
|
key?: string;
|
|
28
29
|
}
|
|
29
30
|
export declare function parseArgs(argv: string[]): CliArgs;
|
|
31
|
+
/**
|
|
32
|
+
* Warning shown at startup when `--remote` binds the tailnet over plain HTTP
|
|
33
|
+
* (no `--https`). Browsers treat plain HTTP over a non-loopback host as an
|
|
34
|
+
* INSECURE context, so service workers and the Notification API are
|
|
35
|
+
* unavailable — PWA install and mobile notifications (iOS exposes no
|
|
36
|
+
* Notification API at all there) silently never work. Loopback (127.0.0.1) is
|
|
37
|
+
* exempt (it counts as secure), so pure-local mode returns no warning.
|
|
38
|
+
*
|
|
39
|
+
* Returns the multi-line warning string, or `undefined` when TLS is enabled or
|
|
40
|
+
* the server is loopback-only. Kept pure (no console) so it is unit-testable.
|
|
41
|
+
*/
|
|
42
|
+
export declare function insecureRemoteWarning(args: Pick<CliArgs, "remote" | "https">): string | undefined;
|
|
43
|
+
type ShutdownWatcher = Pick<ReturnType<typeof watch>, "close">;
|
|
44
|
+
interface ShutdownDeps {
|
|
45
|
+
log: (message: string) => void;
|
|
46
|
+
clearReloadTimer: () => void;
|
|
47
|
+
watchers: Iterable<ShutdownWatcher>;
|
|
48
|
+
closeServer: () => void;
|
|
49
|
+
stopAll: () => Promise<unknown>;
|
|
50
|
+
exit: (code: number) => void;
|
|
51
|
+
}
|
|
52
|
+
export declare function createShutdown(deps: ShutdownDeps): (message: string, exitCode: number) => void;
|
|
53
|
+
type TlsOptions = {
|
|
54
|
+
cert: string;
|
|
55
|
+
key: string;
|
|
56
|
+
};
|
|
57
|
+
type TlsWatchFilename = string | Buffer | null;
|
|
58
|
+
type TlsWatchListener = (event: string, filename: TlsWatchFilename) => void;
|
|
59
|
+
type TlsFileWatcher = ShutdownWatcher & {
|
|
60
|
+
on(event: "error", listener: (err: unknown) => void): unknown;
|
|
61
|
+
};
|
|
62
|
+
interface TlsReloaderDeps {
|
|
63
|
+
readTlsOptions: () => TlsOptions;
|
|
64
|
+
setSecureContext: (options: TlsOptions) => void;
|
|
65
|
+
log: (message: string) => void;
|
|
66
|
+
warn: (message: string) => void;
|
|
67
|
+
}
|
|
68
|
+
export declare function createTlsReloader(deps: TlsReloaderDeps): () => void;
|
|
69
|
+
export declare function shouldReloadForFile(filename: TlsWatchFilename, certBase: string, keyBase: string): boolean;
|
|
70
|
+
interface TlsWatchDeps {
|
|
71
|
+
certPath: string;
|
|
72
|
+
keyPath: string;
|
|
73
|
+
watchDirectory: (directory: string, listener: TlsWatchListener) => TlsFileWatcher;
|
|
74
|
+
reloadTls: () => void;
|
|
75
|
+
warn: (message: string) => void;
|
|
76
|
+
debounceMs?: number;
|
|
77
|
+
}
|
|
78
|
+
interface TlsWatchController {
|
|
79
|
+
watchers: TlsFileWatcher[];
|
|
80
|
+
clearReloadTimer: () => void;
|
|
81
|
+
}
|
|
82
|
+
export declare function createTlsWatchers(deps: TlsWatchDeps): TlsWatchController;
|
|
30
83
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG;AAaH,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC9F,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,mBAAmB,sBAAsB,CAAC;AAI1C,UAAU,OAAO;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CA6CjD","sourcesContent":["#!/usr/bin/env node\n/**\n * dreb-dashboard — launch the dreb web dashboard server.\n *\n * Modes (exactly two, no LAN mode):\n * default loopback bind (127.0.0.1), no auth, no Tailscale needed\n * --remote requires Tailscale; binds all interfaces but every request\n * passes identity allowlist + pairing code + device cookies\n *\n * Usage:\n * dreb-dashboard [--port 5343] [--remote --allow me@example.com [--allow ...]]\n */\n\nimport { existsSync, readFileSync, watch } from \"node:fs\";\nimport { createServer as createHttpServer, type Server as HttpServer } from \"node:http\";\nimport { createServer as createHttpsServer, type Server as HttpsServer } from \"node:https\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { DashboardAuth } from \"./server/auth.js\";\nimport { FilePairingStorage, loadOrCreateDashboardSecret } from \"./server/pairing-storage.js\";\nimport { RuntimePool } from \"./server/runtime-pool.js\";\nimport { createDashboardServer } from \"./server/server.js\";\n\nexport { DashboardAuth, TailscaleStatusResolver } from \"./server/auth.js\";\nexport { EventHub } from \"./server/event-hub.js\";\nexport { canonicalizePath, FileApi } from \"./server/files.js\";\nexport { FilePairingStorage, loadOrCreateDashboardSecret } from \"./server/pairing-storage.js\";\nexport { RuntimePool, resolveDrebCliPath } from \"./server/runtime-pool.js\";\nexport { createDashboardServer, parseDeviceCookie } from \"./server/server.js\";\nexport type * from \"./shared/protocol.js\";\n\nconst DEFAULT_PORT = 5343;\n\ninterface CliArgs {\n\tport: number;\n\tremote: boolean;\n\tallow: string[];\n\thelp: boolean;\n\thttps: boolean;\n\tcert?: string;\n\tkey?: string;\n}\n\nexport function parseArgs(argv: string[]): CliArgs {\n\tconst args: CliArgs = { port: DEFAULT_PORT, remote: false, allow: [], help: false, https: false };\n\tfor (let i = 0; i < argv.length; i++) {\n\t\tconst arg = argv[i];\n\t\tif (arg === \"--port\") {\n\t\t\tconst value = argv[++i];\n\t\t\tconst port = Number.parseInt(value ?? \"\", 10);\n\t\t\tif (!Number.isInteger(port) || port < 1 || port > 65535) {\n\t\t\t\tthrow new Error(`Invalid --port value: ${value}`);\n\t\t\t}\n\t\t\targs.port = port;\n\t\t} else if (arg === \"--remote\") {\n\t\t\targs.remote = true;\n\t\t} else if (arg === \"--allow\") {\n\t\t\tconst value = argv[++i];\n\t\t\tif (!value) throw new Error(\"--allow requires an identity (Tailscale login name)\");\n\t\t\targs.allow.push(value);\n\t\t} else if (arg === \"--https\") {\n\t\t\targs.https = true;\n\t\t} else if (arg === \"--cert\") {\n\t\t\tconst value = argv[++i];\n\t\t\tif (!value) throw new Error(\"--cert requires a path to a PEM certificate file\");\n\t\t\targs.cert = value;\n\t\t} else if (arg === \"--key\") {\n\t\t\tconst value = argv[++i];\n\t\t\tif (!value) throw new Error(\"--key requires a path to a PEM private key file\");\n\t\t\targs.key = value;\n\t\t} else if (arg === \"--help\" || arg === \"-h\") {\n\t\t\targs.help = true;\n\t\t} else {\n\t\t\tthrow new Error(`Unknown argument: ${arg}`);\n\t\t}\n\t}\n\tif (args.remote && args.allow.length === 0) {\n\t\tthrow new Error(\"--remote requires at least one --allow <tailscale-login> (empty allowlist denies everyone)\");\n\t}\n\t// Native TLS. `https` is opt-in; it requires cert AND key (no silent\n\t// fallback to plain HTTP). The dashboard terminates TLS itself — no reverse\n\t// proxy, no auth-model change. `req.socket.remoteAddress` stays the real\n\t// tailnet IP, so identity resolution + allowlist + pairing keep working.\n\tif (args.https) {\n\t\tif (!args.cert) throw new Error(\"--https requires --cert <path>\");\n\t\tif (!args.key) throw new Error(\"--https requires --key <path>\");\n\t}\n\treturn args;\n}\n\nconst HELP = `dreb-dashboard — dreb web dashboard server\n\nUsage: dreb-dashboard [options]\n\nOptions:\n --port <n> Port to listen on (default ${DEFAULT_PORT})\n --remote Enable remote mode (requires Tailscale). Without this\n flag the server binds 127.0.0.1 only — no LAN access.\n --allow <identity> Tailscale login name allowed to pair (repeatable;\n required with --remote)\n --https Terminate TLS on the dashboard itself (native TLS). No\n reverse proxy, no auth-model change. Requires --cert\n and --key. Mainly for --remote (loopback is already a\n secure context): use 'tailscale cert' files for a\n tailnet hostname so mobile PWAs + notifications work.\n NOTE: with --https the server speaks TLS only, so the\n host's plain-http local tab (http://127.0.0.1) stops\n working — use the tailnet hostname (https://...) there.\n --cert <path> PEM certificate file (required with --https)\n --key <path> PEM private key file (required with --https)\n --help Show this help\n`;\n\nasync function main(): Promise<void> {\n\tlet args: CliArgs;\n\ttry {\n\t\targs = parseArgs(process.argv.slice(2));\n\t} catch (err) {\n\t\tconsole.error(`error: ${err instanceof Error ? err.message : String(err)}`);\n\t\tconsole.error(HELP);\n\t\tprocess.exit(1);\n\t}\n\tif (args.help) {\n\t\tconsole.log(HELP);\n\t\treturn;\n\t}\n\n\tconst agentDir = join(homedir(), \".dreb\", \"agent\");\n\tconst auth = new DashboardAuth({\n\t\tremoteEnabled: args.remote,\n\t\tallowedIdentities: args.allow,\n\t\tstorage: new FilePairingStorage(join(agentDir, \"dashboard-pairings.json\")),\n\t\tsecret: loadOrCreateDashboardSecret(join(agentDir, \"dashboard-auth-secret\")),\n\t\tlogger: (line) => console.warn(`[dashboard-auth] ${line}`),\n\t});\n\tconst pool = new RuntimePool();\n\n\t// Static client assets live next to the compiled server (dist/static).\n\tconst staticDir = join(dirname(fileURLToPath(import.meta.url)), \"static\");\n\n\t// The server's own build version (dist/index.js → ../package.json) — surfaced\n\t// in the settings footer so a stale long-running service is spottable.\n\tlet serverVersion: string | undefined;\n\ttry {\n\t\tconst pkgPath = join(dirname(fileURLToPath(import.meta.url)), \"..\", \"package.json\");\n\t\tserverVersion = JSON.parse(readFileSync(pkgPath, \"utf8\")).version;\n\t} catch {\n\t\tserverVersion = undefined;\n\t}\n\n\t// Restart hook: exit non-zero so a supervisor (systemd Restart=on-failure,\n\t// etc.) respawns the process with the freshly-built dist. Assigned the http\n\t// server below via a mutable holder so the closure can close it first.\n\tlet httpServer: HttpServer | HttpsServer | undefined;\n\tconst onRestart = () => {\n\t\tconsole.log(\"restart requested — exiting for supervisor to respawn\");\n\t\thttpServer?.close();\n\t\tpool.stopAll().finally(() => process.exit(1));\n\t};\n\n\tconst { SessionManager } = await import(\"@dreb/coding-agent\");\n\tconst app = createDashboardServer({\n\t\tauth,\n\t\tpool,\n\t\tstaticDir: existsSync(staticDir) ? staticDir : undefined,\n\t\tserverVersion,\n\t\tonRestart,\n\t\tlistAllSessions: () => SessionManager.listAll(),\n\t\tdeleteSession: async (path: string) => {\n\t\t\tconst result = await SessionManager.deleteSession(path, {});\n\t\t\tif (!result.ok) throw new Error(result.error ?? \"Unknown deletion error\");\n\t\t\treturn { method: result.method };\n\t\t},\n\t});\n\n\tconst host = args.remote ? \"0.0.0.0\" : \"127.0.0.1\";\n\tconst scheme = args.https ? \"https\" : \"http\";\n\n\t// Native TLS: the dashboard terminates TLS itself (no reverse proxy). The\n\t// auth model is unchanged — `req.socket.remoteAddress` is still the real\n\t// tailnet IP, so Tailscale identity resolution + allowlist + pairing keep\n\t// working. Local mode never sets --https (loopback is already a secure\n\t// context), but it's allowed if the operator wants it.\n\tlet server: HttpServer | HttpsServer;\n\tif (args.https && args.cert && args.key) {\n\t\tlet tlsOptions: { cert: string; key: string };\n\t\ttry {\n\t\t\ttlsOptions = {\n\t\t\t\tcert: readFileSync(args.cert, \"utf-8\"),\n\t\t\t\tkey: readFileSync(args.key, \"utf-8\"),\n\t\t\t};\n\t\t} catch (err) {\n\t\t\tconsole.error(`error: failed to read TLS cert/key: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tconst httpsServer = createHttpsServer(tlsOptions, app);\n\t\t// Hot-reload the cert on file change (zero-downtime renewal — e.g. a\n\t\t// systemd timer rewrites the `tailscale cert` files daily). New\n\t\t// connections pick up the new cert; existing ones finish on the old.\n\t\tconst reloadTls = () => {\n\t\t\ttry {\n\t\t\t\thttpsServer.setSecureContext({\n\t\t\t\t\tcert: readFileSync(args.cert!, \"utf-8\"),\n\t\t\t\t\tkey: readFileSync(args.key!, \"utf-8\"),\n\t\t\t\t});\n\t\t\t\tconsole.log(\"tls certificate reloaded\");\n\t\t\t} catch (err) {\n\t\t\t\tconsole.warn(`tls reload failed (keeping old cert): ${err instanceof Error ? err.message : String(err)}`);\n\t\t\t}\n\t\t};\n\t\t// Debounce: cert + key are often rewritten in quick succession.\n\t\tlet reloadTimer: ReturnType<typeof setTimeout> | undefined;\n\t\tfor (const file of [args.cert, args.key]) {\n\t\t\twatch(file!, () => {\n\t\t\t\tif (reloadTimer) clearTimeout(reloadTimer);\n\t\t\t\treloadTimer = setTimeout(reloadTls, 500);\n\t\t\t});\n\t\t}\n\t\tserver = httpsServer;\n\t} else {\n\t\tserver = createHttpServer(app);\n\t}\n\tserver.listen(args.port, host, () => {\n\t\tconsole.log(\n\t\t\t`dreb dashboard listening on ${scheme}://${host === \"0.0.0.0\" ? \"<tailscale-ip>\" : host}:${args.port}`,\n\t\t);\n\t\tif (args.remote) {\n\t\t\tconsole.log(`remote mode: allowed identities = ${args.allow.join(\", \")}`);\n\t\t\tconst { code, expiresInMs } = auth.currentPairingCode();\n\t\t\tconsole.log(\n\t\t\t\t`pairing code: ${code} (rotates every 30s; current code rolls in ${Math.ceil(expiresInMs / 1000)}s)`,\n\t\t\t);\n\t\t\tconsole.log(\"new devices enter this code; see it live in dashboard Settings on the host machine\");\n\t\t} else {\n\t\t\tconsole.log(\"local mode: loopback only — use --remote for Tailscale access\");\n\t\t}\n\t\tif (args.https) {\n\t\t\tconsole.log(\"tls: native HTTPS enabled (cert + key hot-reload on file change)\");\n\t\t}\n\t});\n\thttpServer = server;\n\n\tconst shutdown = () => {\n\t\tconsole.log(\"shutting down…\");\n\t\tserver.close();\n\t\tpool.stopAll().finally(() => process.exit(0));\n\t};\n\tprocess.on(\"SIGINT\", shutdown);\n\tprocess.on(\"SIGTERM\", shutdown);\n}\n\n// Only run when executed directly (not when imported for the library exports).\nconst entryPath = process.argv[1];\nif (entryPath && import.meta.url === new URL(`file://${entryPath}`).href) {\n\tmain().catch((err) => {\n\t\tconsole.error(`fatal: ${err instanceof Error ? err.message : String(err)}`);\n\t\tprocess.exit(1);\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * dreb-dashboard — launch the dreb web dashboard server.\n *\n * Modes (exactly two, no LAN mode):\n * default loopback bind (127.0.0.1), no auth, no Tailscale needed\n * --remote requires Tailscale; binds all interfaces but every request\n * passes identity allowlist + pairing code + device cookies\n *\n * Usage:\n * dreb-dashboard [--port 5343] [--remote --allow me@example.com [--allow ...]]\n */\n\nimport { existsSync, readFileSync, watch } from \"node:fs\";\nimport { createServer as createHttpServer, type Server as HttpServer } from \"node:http\";\nimport { createServer as createHttpsServer, type Server as HttpsServer } from \"node:https\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { DashboardAuth } from \"./server/auth.js\";\nimport { FilePairingStorage, loadOrCreateDashboardSecret } from \"./server/pairing-storage.js\";\nimport { RuntimePool } from \"./server/runtime-pool.js\";\nimport { createDashboardServer } from \"./server/server.js\";\n\nexport { DashboardAuth, TailscaleStatusResolver } from \"./server/auth.js\";\nexport { EventHub } from \"./server/event-hub.js\";\nexport { canonicalizePath, FileApi } from \"./server/files.js\";\nexport { FilePairingStorage, loadOrCreateDashboardSecret } from \"./server/pairing-storage.js\";\nexport { RuntimePool, resolveDrebCliPath } from \"./server/runtime-pool.js\";\nexport { createDashboardServer, parseDeviceCookie } from \"./server/server.js\";\nexport type * from \"./shared/protocol.js\";\n\nconst DEFAULT_PORT = 5343;\n\ninterface CliArgs {\n\tport: number;\n\tremote: boolean;\n\tallow: string[];\n\thelp: boolean;\n\thttps: boolean;\n\tcert?: string;\n\tkey?: string;\n}\n\nexport function parseArgs(argv: string[]): CliArgs {\n\tconst args: CliArgs = { port: DEFAULT_PORT, remote: false, allow: [], help: false, https: false };\n\tfor (let i = 0; i < argv.length; i++) {\n\t\tconst arg = argv[i];\n\t\tif (arg === \"--port\") {\n\t\t\tconst value = argv[++i];\n\t\t\tconst port = Number.parseInt(value ?? \"\", 10);\n\t\t\tif (!Number.isInteger(port) || port < 1 || port > 65535) {\n\t\t\t\tthrow new Error(`Invalid --port value: ${value}`);\n\t\t\t}\n\t\t\targs.port = port;\n\t\t} else if (arg === \"--remote\") {\n\t\t\targs.remote = true;\n\t\t} else if (arg === \"--allow\") {\n\t\t\tconst value = argv[++i];\n\t\t\tif (!value) throw new Error(\"--allow requires an identity (Tailscale login name)\");\n\t\t\targs.allow.push(value);\n\t\t} else if (arg === \"--https\") {\n\t\t\targs.https = true;\n\t\t} else if (arg === \"--cert\") {\n\t\t\tconst value = argv[++i];\n\t\t\tif (!value) throw new Error(\"--cert requires a path to a PEM certificate file\");\n\t\t\targs.cert = value;\n\t\t} else if (arg === \"--key\") {\n\t\t\tconst value = argv[++i];\n\t\t\tif (!value) throw new Error(\"--key requires a path to a PEM private key file\");\n\t\t\targs.key = value;\n\t\t} else if (arg === \"--help\" || arg === \"-h\") {\n\t\t\targs.help = true;\n\t\t} else {\n\t\t\tthrow new Error(`Unknown argument: ${arg}`);\n\t\t}\n\t}\n\tif (args.remote && args.allow.length === 0) {\n\t\tthrow new Error(\"--remote requires at least one --allow <tailscale-login> (empty allowlist denies everyone)\");\n\t}\n\t// Native TLS. `https` is opt-in; it requires cert AND key (no silent\n\t// fallback to plain HTTP). The dashboard terminates TLS itself — no reverse\n\t// proxy, no auth-model change. `req.socket.remoteAddress` stays the real\n\t// tailnet IP, so identity resolution + allowlist + pairing keep working.\n\tif (args.https) {\n\t\tif (!args.cert) throw new Error(\"--https requires --cert <path>\");\n\t\tif (!args.key) throw new Error(\"--https requires --key <path>\");\n\t}\n\treturn args;\n}\n\n/**\n * Warning shown at startup when `--remote` binds the tailnet over plain HTTP\n * (no `--https`). Browsers treat plain HTTP over a non-loopback host as an\n * INSECURE context, so service workers and the Notification API are\n * unavailable — PWA install and mobile notifications (iOS exposes no\n * Notification API at all there) silently never work. Loopback (127.0.0.1) is\n * exempt (it counts as secure), so pure-local mode returns no warning.\n *\n * Returns the multi-line warning string, or `undefined` when TLS is enabled or\n * the server is loopback-only. Kept pure (no console) so it is unit-testable.\n */\nexport function insecureRemoteWarning(args: Pick<CliArgs, \"remote\" | \"https\">): string | undefined {\n\tif (!args.remote || args.https) return undefined;\n\treturn (\n\t\t\"⚠ warning: --remote without --https serves plain HTTP over the tailnet, which browsers treat as an\\n\" +\n\t\t\" INSECURE context — service workers, PWA install, and mobile notifications (especially iOS) will NOT work.\\n\" +\n\t\t\" Enable TLS for the tailnet hostname:\\n\" +\n\t\t\" tailscale cert <host>.<tailnet>.ts.net\\n\" +\n\t\t\" …then relaunch with --https --cert <host>.<tailnet>.ts.net.crt --key <host>.<tailnet>.ts.net.key\\n\" +\n\t\t\" and open the dashboard via https://<host>.<tailnet>.ts.net (the hostname, not the tailnet IP).\"\n\t);\n}\n\ntype ShutdownWatcher = Pick<ReturnType<typeof watch>, \"close\">;\n\ninterface ShutdownDeps {\n\tlog: (message: string) => void;\n\tclearReloadTimer: () => void;\n\twatchers: Iterable<ShutdownWatcher>;\n\tcloseServer: () => void;\n\tstopAll: () => Promise<unknown>;\n\texit: (code: number) => void;\n}\n\nexport function createShutdown(deps: ShutdownDeps): (message: string, exitCode: number) => void {\n\tlet shuttingDown = false;\n\treturn (message: string, exitCode: number) => {\n\t\tif (shuttingDown) return;\n\t\tshuttingDown = true;\n\t\tdeps.log(message);\n\t\tdeps.clearReloadTimer();\n\t\tfor (const watcher of deps.watchers) watcher.close();\n\t\tdeps.closeServer();\n\t\tvoid deps\n\t\t\t.stopAll()\n\t\t\t.catch((err) => {\n\t\t\t\tdeps.log(`shutdown teardown failed: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\t})\n\t\t\t.finally(() => deps.exit(exitCode));\n\t};\n}\n\ntype TlsOptions = { cert: string; key: string };\ntype TlsWatchFilename = string | Buffer | null;\ntype TlsWatchListener = (event: string, filename: TlsWatchFilename) => void;\ntype TlsFileWatcher = ShutdownWatcher & {\n\ton(event: \"error\", listener: (err: unknown) => void): unknown;\n};\n\ninterface TlsReloaderDeps {\n\treadTlsOptions: () => TlsOptions;\n\tsetSecureContext: (options: TlsOptions) => void;\n\tlog: (message: string) => void;\n\twarn: (message: string) => void;\n}\n\nexport function createTlsReloader(deps: TlsReloaderDeps): () => void {\n\treturn () => {\n\t\ttry {\n\t\t\tdeps.setSecureContext(deps.readTlsOptions());\n\t\t\tdeps.log(\"tls certificate reloaded\");\n\t\t} catch (err) {\n\t\t\tdeps.warn(`tls reload failed (keeping old cert): ${err instanceof Error ? err.message : String(err)}`);\n\t\t}\n\t};\n}\n\nexport function shouldReloadForFile(filename: TlsWatchFilename, certBase: string, keyBase: string): boolean {\n\tif (filename == null) return true;\n\tif (typeof filename !== \"string\") return false;\n\treturn filename === certBase || filename === keyBase;\n}\n\ninterface TlsWatchDeps {\n\tcertPath: string;\n\tkeyPath: string;\n\twatchDirectory: (directory: string, listener: TlsWatchListener) => TlsFileWatcher;\n\treloadTls: () => void;\n\twarn: (message: string) => void;\n\tdebounceMs?: number;\n}\n\ninterface TlsWatchController {\n\twatchers: TlsFileWatcher[];\n\tclearReloadTimer: () => void;\n}\n\nexport function createTlsWatchers(deps: TlsWatchDeps): TlsWatchController {\n\tlet reloadTimer: ReturnType<typeof setTimeout> | undefined;\n\tconst watchers: TlsFileWatcher[] = [];\n\tconst debounceMs = deps.debounceMs ?? 500;\n\tconst certBase = basename(deps.certPath);\n\tconst keyBase = basename(deps.keyPath);\n\tconst clearReloadTimer = () => {\n\t\tif (!reloadTimer) return;\n\t\tclearTimeout(reloadTimer);\n\t\treloadTimer = undefined;\n\t};\n\tconst scheduleReload = () => {\n\t\tclearReloadTimer();\n\t\treloadTimer = setTimeout(deps.reloadTls, debounceMs);\n\t};\n\n\tfor (const dir of new Set([dirname(deps.certPath), dirname(deps.keyPath)])) {\n\t\ttry {\n\t\t\tconst watcher = deps.watchDirectory(dir, (_event, filename) => {\n\t\t\t\t// `fs.watch` directory events on macOS (FSEvents) frequently fire\n\t\t\t\t// with `filename === null`; fall through and reload anyway.\n\t\t\t\t// On Linux (inotify) the basename is carried reliably and filters\n\t\t\t\t// out unrelated files in the parent directory.\n\t\t\t\tif (!shouldReloadForFile(filename, certBase, keyBase)) return;\n\t\t\t\tscheduleReload();\n\t\t\t});\n\t\t\twatcher.on(\"error\", (err) => {\n\t\t\t\tdeps.warn(`tls cert watch error: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\t});\n\t\t\twatchers.push(watcher);\n\t\t} catch (err) {\n\t\t\tdeps.warn(`tls cert watch setup failed for ${dir}: ${err instanceof Error ? err.message : String(err)}`);\n\t\t}\n\t}\n\n\treturn { watchers, clearReloadTimer };\n}\n\nconst HELP = `dreb-dashboard — dreb web dashboard server\n\nUsage: dreb-dashboard [options]\n\nOptions:\n --port <n> Port to listen on (default ${DEFAULT_PORT})\n --remote Enable remote mode (requires Tailscale). Without this\n flag the server binds 127.0.0.1 only — no LAN access.\n Plain HTTP over the tailnet is an INSECURE context: add\n --https (below) or mobile PWA install + notifications\n (especially iOS) will not work.\n --allow <identity> Tailscale login name allowed to pair (repeatable;\n required with --remote)\n --https Terminate TLS on the dashboard itself (native TLS). No\n reverse proxy, no auth-model change. Requires --cert\n and --key. Mainly for --remote (loopback is already a\n secure context): use 'tailscale cert' files for a\n tailnet hostname so mobile PWAs + notifications work.\n NOTE: with --https the server speaks TLS only, so the\n host's plain-http local tab (http://127.0.0.1) stops\n working — use the tailnet hostname (https://...) there.\n --cert <path> PEM certificate file (required with --https)\n --key <path> PEM private key file (required with --https)\n --help Show this help\n`;\n\nasync function main(): Promise<void> {\n\tlet args: CliArgs;\n\ttry {\n\t\targs = parseArgs(process.argv.slice(2));\n\t} catch (err) {\n\t\tconsole.error(`error: ${err instanceof Error ? err.message : String(err)}`);\n\t\tconsole.error(HELP);\n\t\tprocess.exit(1);\n\t}\n\tif (args.help) {\n\t\tconsole.log(HELP);\n\t\treturn;\n\t}\n\n\tconst agentDir = join(homedir(), \".dreb\", \"agent\");\n\tconst auth = new DashboardAuth({\n\t\tremoteEnabled: args.remote,\n\t\tallowedIdentities: args.allow,\n\t\tstorage: new FilePairingStorage(join(agentDir, \"dashboard-pairings.json\")),\n\t\tsecret: loadOrCreateDashboardSecret(join(agentDir, \"dashboard-auth-secret\")),\n\t\tlogger: (line) => console.warn(`[dashboard-auth] ${line}`),\n\t});\n\tconst pool = new RuntimePool();\n\n\t// Static client assets live next to the compiled server (dist/static).\n\tconst staticDir = join(dirname(fileURLToPath(import.meta.url)), \"static\");\n\n\t// The server's own build version (dist/index.js → ../package.json) — surfaced\n\t// in the settings footer so a stale long-running service is spottable.\n\tlet serverVersion: string | undefined;\n\ttry {\n\t\tconst pkgPath = join(dirname(fileURLToPath(import.meta.url)), \"..\", \"package.json\");\n\t\tserverVersion = JSON.parse(readFileSync(pkgPath, \"utf8\")).version;\n\t} catch {\n\t\tserverVersion = undefined;\n\t}\n\n\t// Restart hook: exit non-zero so a supervisor (systemd Restart=on-failure,\n\t// etc.) respawns the process with the freshly-built dist. Assigned the http\n\t// server below via a mutable holder so the closure can close it first.\n\t// The TLS hot-reload debounce timer and directory watchers are lifted here\n\t// too, so restart and signal shutdown can release them (otherwise they leak\n\t// across restart/exit).\n\tlet httpServer: HttpServer | HttpsServer | undefined;\n\tlet clearTlsReloadTimer = () => {};\n\tconst tlsWatchers: TlsFileWatcher[] = [];\n\tconst beginShutdown = createShutdown({\n\t\tlog: (message) => console.log(message),\n\t\tclearReloadTimer: () => clearTlsReloadTimer(),\n\t\twatchers: tlsWatchers,\n\t\tcloseServer: () => httpServer?.close(),\n\t\tstopAll: () => pool.stopAll(),\n\t\texit: (code) => process.exit(code),\n\t});\n\tconst onRestart = () => beginShutdown(\"restart requested — exiting for supervisor to respawn\", 1);\n\n\tconst { SessionManager } = await import(\"@dreb/coding-agent\");\n\tconst app = createDashboardServer({\n\t\tauth,\n\t\tpool,\n\t\tstaticDir: existsSync(staticDir) ? staticDir : undefined,\n\t\tserverVersion,\n\t\tonRestart,\n\t\tlistAllSessions: () => SessionManager.listAll(),\n\t\tdeleteSession: async (path: string) => {\n\t\t\tconst result = await SessionManager.deleteSession(path, {});\n\t\t\tif (!result.ok) throw new Error(result.error ?? \"Unknown deletion error\");\n\t\t\treturn { method: result.method };\n\t\t},\n\t});\n\n\tconst host = args.remote ? \"0.0.0.0\" : \"127.0.0.1\";\n\tconst scheme = args.https ? \"https\" : \"http\";\n\n\t// Native TLS: the dashboard terminates TLS itself (no reverse proxy). The\n\t// auth model is unchanged — `req.socket.remoteAddress` is still the real\n\t// tailnet IP, so Tailscale identity resolution + allowlist + pairing keep\n\t// working. Local mode never sets --https (loopback is already a secure\n\t// context), but it's allowed if the operator wants it.\n\tlet server: HttpServer | HttpsServer;\n\tif (args.https && args.cert && args.key) {\n\t\tconst readTlsOptions = () => ({\n\t\t\tcert: readFileSync(args.cert!, \"utf-8\"),\n\t\t\tkey: readFileSync(args.key!, \"utf-8\"),\n\t\t});\n\t\tlet tlsOptions: TlsOptions;\n\t\ttry {\n\t\t\ttlsOptions = readTlsOptions();\n\t\t} catch (err) {\n\t\t\tconsole.error(`error: failed to read TLS cert/key: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tconst httpsServer = createHttpsServer(tlsOptions, app);\n\t\t// Hot-reload the cert on file change (zero-downtime renewal — e.g. a\n\t\t// systemd timer rewrites the `tailscale cert` files daily). New\n\t\t// connections pick up the new cert; existing ones finish on the old.\n\t\tconst reloadTls = createTlsReloader({\n\t\t\treadTlsOptions,\n\t\t\tsetSecureContext: (options) => httpsServer.setSecureContext(options),\n\t\t\tlog: (message) => console.log(message),\n\t\t\twarn: (message) => console.warn(message),\n\t\t});\n\t\t// Debounce: cert + key are often rewritten in quick succession. Watch\n\t\t// the PARENT DIRECTORY (not each file) and filter by filename: `fs.watch`\n\t\t// follows the inode, so an atomic renewal (write temp + rename(2))\n\t\t// replaces the inode and a file-level watcher goes silent on the new\n\t\t// file after the first reload. A directory watcher survives renames and\n\t\t// reports the changed basename via the `filename` callback argument.\n\t\tconst tlsWatchController = createTlsWatchers({\n\t\t\tcertPath: args.cert,\n\t\t\tkeyPath: args.key,\n\t\t\twatchDirectory: (dir, listener) => watch(dir, listener),\n\t\t\treloadTls,\n\t\t\twarn: (message) => console.warn(message),\n\t\t});\n\t\tclearTlsReloadTimer = tlsWatchController.clearReloadTimer;\n\t\ttlsWatchers.push(...tlsWatchController.watchers);\n\t\tserver = httpsServer;\n\t} else {\n\t\tserver = createHttpServer(app);\n\t}\n\tserver.listen(args.port, host, () => {\n\t\tconsole.log(\n\t\t\t`dreb dashboard listening on ${scheme}://${host === \"0.0.0.0\" ? \"<tailscale-ip>\" : host}:${args.port}`,\n\t\t);\n\t\tif (args.remote) {\n\t\t\tconsole.log(`remote mode: allowed identities = ${args.allow.join(\", \")}`);\n\t\t\tconst { code, expiresInMs } = auth.currentPairingCode();\n\t\t\tconsole.log(\n\t\t\t\t`pairing code: ${code} (rotates every 30s; current code rolls in ${Math.ceil(expiresInMs / 1000)}s)`,\n\t\t\t);\n\t\t\tconsole.log(\"new devices enter this code; see it live in dashboard Settings on the host machine\");\n\t\t} else {\n\t\t\tconsole.log(\"local mode: loopback only — use --remote for Tailscale access\");\n\t\t}\n\t\tif (args.https) {\n\t\t\tconsole.log(\"tls: native HTTPS enabled (cert + key hot-reload on file change)\");\n\t\t} else {\n\t\t\tconst insecureWarning = insecureRemoteWarning(args);\n\t\t\tif (insecureWarning) console.warn(insecureWarning);\n\t\t}\n\t});\n\thttpServer = server;\n\n\tconst shutdown = () => beginShutdown(\"shutting down…\", 0);\n\tprocess.on(\"SIGINT\", shutdown);\n\tprocess.on(\"SIGTERM\", shutdown);\n}\n\n// Only run when executed directly (not when imported for the library exports).\nconst entryPath = process.argv[1];\nif (entryPath && import.meta.url === new URL(`file://${entryPath}`).href) {\n\tmain().catch((err) => {\n\t\tconsole.error(`fatal: ${err instanceof Error ? err.message : String(err)}`);\n\t\tprocess.exit(1);\n\t});\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { existsSync, readFileSync, watch } from "node:fs";
|
|
|
14
14
|
import { createServer as createHttpServer } from "node:http";
|
|
15
15
|
import { createServer as createHttpsServer } from "node:https";
|
|
16
16
|
import { homedir } from "node:os";
|
|
17
|
-
import { dirname, join } from "node:path";
|
|
17
|
+
import { basename, dirname, join } from "node:path";
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
19
|
import { DashboardAuth } from "./server/auth.js";
|
|
20
20
|
import { FilePairingStorage, loadOrCreateDashboardSecret } from "./server/pairing-storage.js";
|
|
@@ -85,6 +85,102 @@ export function parseArgs(argv) {
|
|
|
85
85
|
}
|
|
86
86
|
return args;
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Warning shown at startup when `--remote` binds the tailnet over plain HTTP
|
|
90
|
+
* (no `--https`). Browsers treat plain HTTP over a non-loopback host as an
|
|
91
|
+
* INSECURE context, so service workers and the Notification API are
|
|
92
|
+
* unavailable — PWA install and mobile notifications (iOS exposes no
|
|
93
|
+
* Notification API at all there) silently never work. Loopback (127.0.0.1) is
|
|
94
|
+
* exempt (it counts as secure), so pure-local mode returns no warning.
|
|
95
|
+
*
|
|
96
|
+
* Returns the multi-line warning string, or `undefined` when TLS is enabled or
|
|
97
|
+
* the server is loopback-only. Kept pure (no console) so it is unit-testable.
|
|
98
|
+
*/
|
|
99
|
+
export function insecureRemoteWarning(args) {
|
|
100
|
+
if (!args.remote || args.https)
|
|
101
|
+
return undefined;
|
|
102
|
+
return ("⚠ warning: --remote without --https serves plain HTTP over the tailnet, which browsers treat as an\n" +
|
|
103
|
+
" INSECURE context — service workers, PWA install, and mobile notifications (especially iOS) will NOT work.\n" +
|
|
104
|
+
" Enable TLS for the tailnet hostname:\n" +
|
|
105
|
+
" tailscale cert <host>.<tailnet>.ts.net\n" +
|
|
106
|
+
" …then relaunch with --https --cert <host>.<tailnet>.ts.net.crt --key <host>.<tailnet>.ts.net.key\n" +
|
|
107
|
+
" and open the dashboard via https://<host>.<tailnet>.ts.net (the hostname, not the tailnet IP).");
|
|
108
|
+
}
|
|
109
|
+
export function createShutdown(deps) {
|
|
110
|
+
let shuttingDown = false;
|
|
111
|
+
return (message, exitCode) => {
|
|
112
|
+
if (shuttingDown)
|
|
113
|
+
return;
|
|
114
|
+
shuttingDown = true;
|
|
115
|
+
deps.log(message);
|
|
116
|
+
deps.clearReloadTimer();
|
|
117
|
+
for (const watcher of deps.watchers)
|
|
118
|
+
watcher.close();
|
|
119
|
+
deps.closeServer();
|
|
120
|
+
void deps
|
|
121
|
+
.stopAll()
|
|
122
|
+
.catch((err) => {
|
|
123
|
+
deps.log(`shutdown teardown failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
124
|
+
})
|
|
125
|
+
.finally(() => deps.exit(exitCode));
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
export function createTlsReloader(deps) {
|
|
129
|
+
return () => {
|
|
130
|
+
try {
|
|
131
|
+
deps.setSecureContext(deps.readTlsOptions());
|
|
132
|
+
deps.log("tls certificate reloaded");
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
deps.warn(`tls reload failed (keeping old cert): ${err instanceof Error ? err.message : String(err)}`);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export function shouldReloadForFile(filename, certBase, keyBase) {
|
|
140
|
+
if (filename == null)
|
|
141
|
+
return true;
|
|
142
|
+
if (typeof filename !== "string")
|
|
143
|
+
return false;
|
|
144
|
+
return filename === certBase || filename === keyBase;
|
|
145
|
+
}
|
|
146
|
+
export function createTlsWatchers(deps) {
|
|
147
|
+
let reloadTimer;
|
|
148
|
+
const watchers = [];
|
|
149
|
+
const debounceMs = deps.debounceMs ?? 500;
|
|
150
|
+
const certBase = basename(deps.certPath);
|
|
151
|
+
const keyBase = basename(deps.keyPath);
|
|
152
|
+
const clearReloadTimer = () => {
|
|
153
|
+
if (!reloadTimer)
|
|
154
|
+
return;
|
|
155
|
+
clearTimeout(reloadTimer);
|
|
156
|
+
reloadTimer = undefined;
|
|
157
|
+
};
|
|
158
|
+
const scheduleReload = () => {
|
|
159
|
+
clearReloadTimer();
|
|
160
|
+
reloadTimer = setTimeout(deps.reloadTls, debounceMs);
|
|
161
|
+
};
|
|
162
|
+
for (const dir of new Set([dirname(deps.certPath), dirname(deps.keyPath)])) {
|
|
163
|
+
try {
|
|
164
|
+
const watcher = deps.watchDirectory(dir, (_event, filename) => {
|
|
165
|
+
// `fs.watch` directory events on macOS (FSEvents) frequently fire
|
|
166
|
+
// with `filename === null`; fall through and reload anyway.
|
|
167
|
+
// On Linux (inotify) the basename is carried reliably and filters
|
|
168
|
+
// out unrelated files in the parent directory.
|
|
169
|
+
if (!shouldReloadForFile(filename, certBase, keyBase))
|
|
170
|
+
return;
|
|
171
|
+
scheduleReload();
|
|
172
|
+
});
|
|
173
|
+
watcher.on("error", (err) => {
|
|
174
|
+
deps.warn(`tls cert watch error: ${err instanceof Error ? err.message : String(err)}`);
|
|
175
|
+
});
|
|
176
|
+
watchers.push(watcher);
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
deps.warn(`tls cert watch setup failed for ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { watchers, clearReloadTimer };
|
|
183
|
+
}
|
|
88
184
|
const HELP = `dreb-dashboard — dreb web dashboard server
|
|
89
185
|
|
|
90
186
|
Usage: dreb-dashboard [options]
|
|
@@ -93,6 +189,9 @@ Options:
|
|
|
93
189
|
--port <n> Port to listen on (default ${DEFAULT_PORT})
|
|
94
190
|
--remote Enable remote mode (requires Tailscale). Without this
|
|
95
191
|
flag the server binds 127.0.0.1 only — no LAN access.
|
|
192
|
+
Plain HTTP over the tailnet is an INSECURE context: add
|
|
193
|
+
--https (below) or mobile PWA install + notifications
|
|
194
|
+
(especially iOS) will not work.
|
|
96
195
|
--allow <identity> Tailscale login name allowed to pair (repeatable;
|
|
97
196
|
required with --remote)
|
|
98
197
|
--https Terminate TLS on the dashboard itself (native TLS). No
|
|
@@ -145,12 +244,21 @@ async function main() {
|
|
|
145
244
|
// Restart hook: exit non-zero so a supervisor (systemd Restart=on-failure,
|
|
146
245
|
// etc.) respawns the process with the freshly-built dist. Assigned the http
|
|
147
246
|
// server below via a mutable holder so the closure can close it first.
|
|
247
|
+
// The TLS hot-reload debounce timer and directory watchers are lifted here
|
|
248
|
+
// too, so restart and signal shutdown can release them (otherwise they leak
|
|
249
|
+
// across restart/exit).
|
|
148
250
|
let httpServer;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
251
|
+
let clearTlsReloadTimer = () => { };
|
|
252
|
+
const tlsWatchers = [];
|
|
253
|
+
const beginShutdown = createShutdown({
|
|
254
|
+
log: (message) => console.log(message),
|
|
255
|
+
clearReloadTimer: () => clearTlsReloadTimer(),
|
|
256
|
+
watchers: tlsWatchers,
|
|
257
|
+
closeServer: () => httpServer?.close(),
|
|
258
|
+
stopAll: () => pool.stopAll(),
|
|
259
|
+
exit: (code) => process.exit(code),
|
|
260
|
+
});
|
|
261
|
+
const onRestart = () => beginShutdown("restart requested — exiting for supervisor to respawn", 1);
|
|
154
262
|
const { SessionManager } = await import("@dreb/coding-agent");
|
|
155
263
|
const app = createDashboardServer({
|
|
156
264
|
auth,
|
|
@@ -175,12 +283,13 @@ async function main() {
|
|
|
175
283
|
// context), but it's allowed if the operator wants it.
|
|
176
284
|
let server;
|
|
177
285
|
if (args.https && args.cert && args.key) {
|
|
286
|
+
const readTlsOptions = () => ({
|
|
287
|
+
cert: readFileSync(args.cert, "utf-8"),
|
|
288
|
+
key: readFileSync(args.key, "utf-8"),
|
|
289
|
+
});
|
|
178
290
|
let tlsOptions;
|
|
179
291
|
try {
|
|
180
|
-
tlsOptions =
|
|
181
|
-
cert: readFileSync(args.cert, "utf-8"),
|
|
182
|
-
key: readFileSync(args.key, "utf-8"),
|
|
183
|
-
};
|
|
292
|
+
tlsOptions = readTlsOptions();
|
|
184
293
|
}
|
|
185
294
|
catch (err) {
|
|
186
295
|
console.error(`error: failed to read TLS cert/key: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -190,27 +299,27 @@ async function main() {
|
|
|
190
299
|
// Hot-reload the cert on file change (zero-downtime renewal — e.g. a
|
|
191
300
|
// systemd timer rewrites the `tailscale cert` files daily). New
|
|
192
301
|
// connections pick up the new cert; existing ones finish on the old.
|
|
193
|
-
const reloadTls = (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
302
|
+
const reloadTls = createTlsReloader({
|
|
303
|
+
readTlsOptions,
|
|
304
|
+
setSecureContext: (options) => httpsServer.setSecureContext(options),
|
|
305
|
+
log: (message) => console.log(message),
|
|
306
|
+
warn: (message) => console.warn(message),
|
|
307
|
+
});
|
|
308
|
+
// Debounce: cert + key are often rewritten in quick succession. Watch
|
|
309
|
+
// the PARENT DIRECTORY (not each file) and filter by filename: `fs.watch`
|
|
310
|
+
// follows the inode, so an atomic renewal (write temp + rename(2))
|
|
311
|
+
// replaces the inode and a file-level watcher goes silent on the new
|
|
312
|
+
// file after the first reload. A directory watcher survives renames and
|
|
313
|
+
// reports the changed basename via the `filename` callback argument.
|
|
314
|
+
const tlsWatchController = createTlsWatchers({
|
|
315
|
+
certPath: args.cert,
|
|
316
|
+
keyPath: args.key,
|
|
317
|
+
watchDirectory: (dir, listener) => watch(dir, listener),
|
|
318
|
+
reloadTls,
|
|
319
|
+
warn: (message) => console.warn(message),
|
|
320
|
+
});
|
|
321
|
+
clearTlsReloadTimer = tlsWatchController.clearReloadTimer;
|
|
322
|
+
tlsWatchers.push(...tlsWatchController.watchers);
|
|
214
323
|
server = httpsServer;
|
|
215
324
|
}
|
|
216
325
|
else {
|
|
@@ -230,13 +339,14 @@ async function main() {
|
|
|
230
339
|
if (args.https) {
|
|
231
340
|
console.log("tls: native HTTPS enabled (cert + key hot-reload on file change)");
|
|
232
341
|
}
|
|
342
|
+
else {
|
|
343
|
+
const insecureWarning = insecureRemoteWarning(args);
|
|
344
|
+
if (insecureWarning)
|
|
345
|
+
console.warn(insecureWarning);
|
|
346
|
+
}
|
|
233
347
|
});
|
|
234
348
|
httpServer = server;
|
|
235
|
-
const shutdown = () =>
|
|
236
|
-
console.log("shutting down…");
|
|
237
|
-
server.close();
|
|
238
|
-
pool.stopAll().finally(() => process.exit(0));
|
|
239
|
-
};
|
|
349
|
+
const shutdown = () => beginShutdown("shutting down…", 0);
|
|
240
350
|
process.on("SIGINT", shutdown);
|
|
241
351
|
process.on("SIGTERM", shutdown);
|
|
242
352
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,YAAY,IAAI,gBAAgB,EAA6B,MAAM,WAAW,CAAC;AACxF,OAAO,EAAE,YAAY,IAAI,iBAAiB,EAA8B,MAAM,YAAY,CAAC;AAC3F,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC9F,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC9F,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAG9E,MAAM,YAAY,GAAG,IAAI,CAAC;AAY1B,MAAM,UAAU,SAAS,CAAC,IAAc,EAAW;IAClD,MAAM,IAAI,GAAY,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAClG,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;YACtB,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACxB,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;YAC9C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;gBACzD,MAAM,IAAI,KAAK,CAAC,yBAAyB,KAAK,EAAE,CAAC,CAAC;YACnD,CAAC;YACD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QAClB,CAAC;aAAM,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;YAC/B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACpB,CAAC;aAAM,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACxB,IAAI,CAAC,KAAK;gBAAE,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;YACnF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;aAAM,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YAC9B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACnB,CAAC;aAAM,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACxB,IAAI,CAAC,KAAK;gBAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;YAChF,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;QACnB,CAAC;aAAM,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;YAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACxB,IAAI,CAAC,KAAK;gBAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;YAC/E,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC;QAClB,CAAC;aAAM,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YAC7C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QAClB,CAAC;aAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;QAC7C,CAAC;IACF,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,4FAA4F,CAAC,CAAC;IAC/G,CAAC;IACD,qEAAqE;IACrE,8EAA4E;IAC5E,yEAAyE;IACzE,yEAAyE;IACzE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAChB,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QAClE,IAAI,CAAC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,MAAM,IAAI,GAAG;;;;;mDAKsC,YAAY;;;;;;;;;;;;;;;;CAgB9D,CAAC;AAEF,KAAK,UAAU,IAAI,GAAkB;IACpC,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACJ,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5E,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO;IACR,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,IAAI,aAAa,CAAC;QAC9B,aAAa,EAAE,IAAI,CAAC,MAAM;QAC1B,iBAAiB,EAAE,IAAI,CAAC,KAAK;QAC7B,OAAO,EAAE,IAAI,kBAAkB,CAAC,IAAI,CAAC,QAAQ,EAAE,yBAAyB,CAAC,CAAC;QAC1E,MAAM,EAAE,2BAA2B,CAAC,IAAI,CAAC,QAAQ,EAAE,uBAAuB,CAAC,CAAC;QAC5E,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,oBAAoB,IAAI,EAAE,CAAC;KAC1D,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC;IAE/B,uEAAuE;IACvE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAE1E,kFAA8E;IAC9E,uEAAuE;IACvE,IAAI,aAAiC,CAAC;IACtC,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QACpF,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACR,aAAa,GAAG,SAAS,CAAC;IAC3B,CAAC;IAED,2EAA2E;IAC3E,4EAA4E;IAC5E,uEAAuE;IACvE,IAAI,UAAgD,CAAC;IACrD,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,yDAAuD,CAAC,CAAC;QACrE,UAAU,EAAE,KAAK,EAAE,CAAC;QACpB,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAAA,CAC9C,CAAC;IAEF,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAC9D,MAAM,GAAG,GAAG,qBAAqB,CAAC;QACjC,IAAI;QACJ,IAAI;QACJ,SAAS,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;QACxD,aAAa;QACb,SAAS;QACT,eAAe,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE;QAC/C,aAAa,EAAE,KAAK,EAAE,IAAY,EAAE,EAAE,CAAC;YACtC,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC5D,IAAI,CAAC,MAAM,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,wBAAwB,CAAC,CAAC;YAC1E,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QAAA,CACjC;KACD,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;IAE7C,0EAA0E;IAC1E,2EAAyE;IACzE,0EAA0E;IAC1E,uEAAuE;IACvE,uDAAuD;IACvD,IAAI,MAAgC,CAAC;IACrC,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACzC,IAAI,UAAyC,CAAC;QAC9C,IAAI,CAAC;YACJ,UAAU,GAAG;gBACZ,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC;gBACtC,GAAG,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC;aACpC,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,uCAAuC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACzG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;QACD,MAAM,WAAW,GAAG,iBAAiB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACvD,uEAAqE;QACrE,gEAAgE;QAChE,qEAAqE;QACrE,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC;YACvB,IAAI,CAAC;gBACJ,WAAW,CAAC,gBAAgB,CAAC;oBAC5B,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,IAAK,EAAE,OAAO,CAAC;oBACvC,GAAG,EAAE,YAAY,CAAC,IAAI,CAAC,GAAI,EAAE,OAAO,CAAC;iBACrC,CAAC,CAAC;gBACH,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;YACzC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,OAAO,CAAC,IAAI,CAAC,yCAAyC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3G,CAAC;QAAA,CACD,CAAC;QACF,gEAAgE;QAChE,IAAI,WAAsD,CAAC;QAC3D,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1C,KAAK,CAAC,IAAK,EAAE,GAAG,EAAE,CAAC;gBAClB,IAAI,WAAW;oBAAE,YAAY,CAAC,WAAW,CAAC,CAAC;gBAC3C,WAAW,GAAG,UAAU,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YAAA,CACzC,CAAC,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,WAAW,CAAC;IACtB,CAAC;SAAM,CAAC;QACP,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IACD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QACpC,OAAO,CAAC,GAAG,CACV,+BAA+B,MAAM,MAAM,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CACtG,CAAC;QACF,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,qCAAqC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC1E,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACxD,OAAO,CAAC,GAAG,CACV,iBAAiB,IAAI,8CAA8C,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CACpG,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,oFAAoF,CAAC,CAAC;QACnG,CAAC;aAAM,CAAC;YACP,OAAO,CAAC,GAAG,CAAC,iEAA+D,CAAC,CAAC;QAC9E,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,kEAAkE,CAAC,CAAC;QACjF,CAAC;IAAA,CACD,CAAC,CAAC;IACH,UAAU,GAAG,MAAM,CAAC;IAEpB,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,kBAAgB,CAAC,CAAC;QAC9B,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAAA,CAC9C,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAAA,CAChC;AAED,+EAA+E;AAC/E,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClC,IAAI,SAAS,IAAI,OAAO,IAAI,CAAC,GAAG,KAAK,IAAI,GAAG,CAAC,UAAU,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1E,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC;QACrB,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAAA,CAChB,CAAC,CAAC;AACJ,CAAC","sourcesContent":["#!/usr/bin/env node\n/**\n * dreb-dashboard — launch the dreb web dashboard server.\n *\n * Modes (exactly two, no LAN mode):\n * default loopback bind (127.0.0.1), no auth, no Tailscale needed\n * --remote requires Tailscale; binds all interfaces but every request\n * passes identity allowlist + pairing code + device cookies\n *\n * Usage:\n * dreb-dashboard [--port 5343] [--remote --allow me@example.com [--allow ...]]\n */\n\nimport { existsSync, readFileSync, watch } from \"node:fs\";\nimport { createServer as createHttpServer, type Server as HttpServer } from \"node:http\";\nimport { createServer as createHttpsServer, type Server as HttpsServer } from \"node:https\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { DashboardAuth } from \"./server/auth.js\";\nimport { FilePairingStorage, loadOrCreateDashboardSecret } from \"./server/pairing-storage.js\";\nimport { RuntimePool } from \"./server/runtime-pool.js\";\nimport { createDashboardServer } from \"./server/server.js\";\n\nexport { DashboardAuth, TailscaleStatusResolver } from \"./server/auth.js\";\nexport { EventHub } from \"./server/event-hub.js\";\nexport { canonicalizePath, FileApi } from \"./server/files.js\";\nexport { FilePairingStorage, loadOrCreateDashboardSecret } from \"./server/pairing-storage.js\";\nexport { RuntimePool, resolveDrebCliPath } from \"./server/runtime-pool.js\";\nexport { createDashboardServer, parseDeviceCookie } from \"./server/server.js\";\nexport type * from \"./shared/protocol.js\";\n\nconst DEFAULT_PORT = 5343;\n\ninterface CliArgs {\n\tport: number;\n\tremote: boolean;\n\tallow: string[];\n\thelp: boolean;\n\thttps: boolean;\n\tcert?: string;\n\tkey?: string;\n}\n\nexport function parseArgs(argv: string[]): CliArgs {\n\tconst args: CliArgs = { port: DEFAULT_PORT, remote: false, allow: [], help: false, https: false };\n\tfor (let i = 0; i < argv.length; i++) {\n\t\tconst arg = argv[i];\n\t\tif (arg === \"--port\") {\n\t\t\tconst value = argv[++i];\n\t\t\tconst port = Number.parseInt(value ?? \"\", 10);\n\t\t\tif (!Number.isInteger(port) || port < 1 || port > 65535) {\n\t\t\t\tthrow new Error(`Invalid --port value: ${value}`);\n\t\t\t}\n\t\t\targs.port = port;\n\t\t} else if (arg === \"--remote\") {\n\t\t\targs.remote = true;\n\t\t} else if (arg === \"--allow\") {\n\t\t\tconst value = argv[++i];\n\t\t\tif (!value) throw new Error(\"--allow requires an identity (Tailscale login name)\");\n\t\t\targs.allow.push(value);\n\t\t} else if (arg === \"--https\") {\n\t\t\targs.https = true;\n\t\t} else if (arg === \"--cert\") {\n\t\t\tconst value = argv[++i];\n\t\t\tif (!value) throw new Error(\"--cert requires a path to a PEM certificate file\");\n\t\t\targs.cert = value;\n\t\t} else if (arg === \"--key\") {\n\t\t\tconst value = argv[++i];\n\t\t\tif (!value) throw new Error(\"--key requires a path to a PEM private key file\");\n\t\t\targs.key = value;\n\t\t} else if (arg === \"--help\" || arg === \"-h\") {\n\t\t\targs.help = true;\n\t\t} else {\n\t\t\tthrow new Error(`Unknown argument: ${arg}`);\n\t\t}\n\t}\n\tif (args.remote && args.allow.length === 0) {\n\t\tthrow new Error(\"--remote requires at least one --allow <tailscale-login> (empty allowlist denies everyone)\");\n\t}\n\t// Native TLS. `https` is opt-in; it requires cert AND key (no silent\n\t// fallback to plain HTTP). The dashboard terminates TLS itself — no reverse\n\t// proxy, no auth-model change. `req.socket.remoteAddress` stays the real\n\t// tailnet IP, so identity resolution + allowlist + pairing keep working.\n\tif (args.https) {\n\t\tif (!args.cert) throw new Error(\"--https requires --cert <path>\");\n\t\tif (!args.key) throw new Error(\"--https requires --key <path>\");\n\t}\n\treturn args;\n}\n\nconst HELP = `dreb-dashboard — dreb web dashboard server\n\nUsage: dreb-dashboard [options]\n\nOptions:\n --port <n> Port to listen on (default ${DEFAULT_PORT})\n --remote Enable remote mode (requires Tailscale). Without this\n flag the server binds 127.0.0.1 only — no LAN access.\n --allow <identity> Tailscale login name allowed to pair (repeatable;\n required with --remote)\n --https Terminate TLS on the dashboard itself (native TLS). No\n reverse proxy, no auth-model change. Requires --cert\n and --key. Mainly for --remote (loopback is already a\n secure context): use 'tailscale cert' files for a\n tailnet hostname so mobile PWAs + notifications work.\n NOTE: with --https the server speaks TLS only, so the\n host's plain-http local tab (http://127.0.0.1) stops\n working — use the tailnet hostname (https://...) there.\n --cert <path> PEM certificate file (required with --https)\n --key <path> PEM private key file (required with --https)\n --help Show this help\n`;\n\nasync function main(): Promise<void> {\n\tlet args: CliArgs;\n\ttry {\n\t\targs = parseArgs(process.argv.slice(2));\n\t} catch (err) {\n\t\tconsole.error(`error: ${err instanceof Error ? err.message : String(err)}`);\n\t\tconsole.error(HELP);\n\t\tprocess.exit(1);\n\t}\n\tif (args.help) {\n\t\tconsole.log(HELP);\n\t\treturn;\n\t}\n\n\tconst agentDir = join(homedir(), \".dreb\", \"agent\");\n\tconst auth = new DashboardAuth({\n\t\tremoteEnabled: args.remote,\n\t\tallowedIdentities: args.allow,\n\t\tstorage: new FilePairingStorage(join(agentDir, \"dashboard-pairings.json\")),\n\t\tsecret: loadOrCreateDashboardSecret(join(agentDir, \"dashboard-auth-secret\")),\n\t\tlogger: (line) => console.warn(`[dashboard-auth] ${line}`),\n\t});\n\tconst pool = new RuntimePool();\n\n\t// Static client assets live next to the compiled server (dist/static).\n\tconst staticDir = join(dirname(fileURLToPath(import.meta.url)), \"static\");\n\n\t// The server's own build version (dist/index.js → ../package.json) — surfaced\n\t// in the settings footer so a stale long-running service is spottable.\n\tlet serverVersion: string | undefined;\n\ttry {\n\t\tconst pkgPath = join(dirname(fileURLToPath(import.meta.url)), \"..\", \"package.json\");\n\t\tserverVersion = JSON.parse(readFileSync(pkgPath, \"utf8\")).version;\n\t} catch {\n\t\tserverVersion = undefined;\n\t}\n\n\t// Restart hook: exit non-zero so a supervisor (systemd Restart=on-failure,\n\t// etc.) respawns the process with the freshly-built dist. Assigned the http\n\t// server below via a mutable holder so the closure can close it first.\n\tlet httpServer: HttpServer | HttpsServer | undefined;\n\tconst onRestart = () => {\n\t\tconsole.log(\"restart requested — exiting for supervisor to respawn\");\n\t\thttpServer?.close();\n\t\tpool.stopAll().finally(() => process.exit(1));\n\t};\n\n\tconst { SessionManager } = await import(\"@dreb/coding-agent\");\n\tconst app = createDashboardServer({\n\t\tauth,\n\t\tpool,\n\t\tstaticDir: existsSync(staticDir) ? staticDir : undefined,\n\t\tserverVersion,\n\t\tonRestart,\n\t\tlistAllSessions: () => SessionManager.listAll(),\n\t\tdeleteSession: async (path: string) => {\n\t\t\tconst result = await SessionManager.deleteSession(path, {});\n\t\t\tif (!result.ok) throw new Error(result.error ?? \"Unknown deletion error\");\n\t\t\treturn { method: result.method };\n\t\t},\n\t});\n\n\tconst host = args.remote ? \"0.0.0.0\" : \"127.0.0.1\";\n\tconst scheme = args.https ? \"https\" : \"http\";\n\n\t// Native TLS: the dashboard terminates TLS itself (no reverse proxy). The\n\t// auth model is unchanged — `req.socket.remoteAddress` is still the real\n\t// tailnet IP, so Tailscale identity resolution + allowlist + pairing keep\n\t// working. Local mode never sets --https (loopback is already a secure\n\t// context), but it's allowed if the operator wants it.\n\tlet server: HttpServer | HttpsServer;\n\tif (args.https && args.cert && args.key) {\n\t\tlet tlsOptions: { cert: string; key: string };\n\t\ttry {\n\t\t\ttlsOptions = {\n\t\t\t\tcert: readFileSync(args.cert, \"utf-8\"),\n\t\t\t\tkey: readFileSync(args.key, \"utf-8\"),\n\t\t\t};\n\t\t} catch (err) {\n\t\t\tconsole.error(`error: failed to read TLS cert/key: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tconst httpsServer = createHttpsServer(tlsOptions, app);\n\t\t// Hot-reload the cert on file change (zero-downtime renewal — e.g. a\n\t\t// systemd timer rewrites the `tailscale cert` files daily). New\n\t\t// connections pick up the new cert; existing ones finish on the old.\n\t\tconst reloadTls = () => {\n\t\t\ttry {\n\t\t\t\thttpsServer.setSecureContext({\n\t\t\t\t\tcert: readFileSync(args.cert!, \"utf-8\"),\n\t\t\t\t\tkey: readFileSync(args.key!, \"utf-8\"),\n\t\t\t\t});\n\t\t\t\tconsole.log(\"tls certificate reloaded\");\n\t\t\t} catch (err) {\n\t\t\t\tconsole.warn(`tls reload failed (keeping old cert): ${err instanceof Error ? err.message : String(err)}`);\n\t\t\t}\n\t\t};\n\t\t// Debounce: cert + key are often rewritten in quick succession.\n\t\tlet reloadTimer: ReturnType<typeof setTimeout> | undefined;\n\t\tfor (const file of [args.cert, args.key]) {\n\t\t\twatch(file!, () => {\n\t\t\t\tif (reloadTimer) clearTimeout(reloadTimer);\n\t\t\t\treloadTimer = setTimeout(reloadTls, 500);\n\t\t\t});\n\t\t}\n\t\tserver = httpsServer;\n\t} else {\n\t\tserver = createHttpServer(app);\n\t}\n\tserver.listen(args.port, host, () => {\n\t\tconsole.log(\n\t\t\t`dreb dashboard listening on ${scheme}://${host === \"0.0.0.0\" ? \"<tailscale-ip>\" : host}:${args.port}`,\n\t\t);\n\t\tif (args.remote) {\n\t\t\tconsole.log(`remote mode: allowed identities = ${args.allow.join(\", \")}`);\n\t\t\tconst { code, expiresInMs } = auth.currentPairingCode();\n\t\t\tconsole.log(\n\t\t\t\t`pairing code: ${code} (rotates every 30s; current code rolls in ${Math.ceil(expiresInMs / 1000)}s)`,\n\t\t\t);\n\t\t\tconsole.log(\"new devices enter this code; see it live in dashboard Settings on the host machine\");\n\t\t} else {\n\t\t\tconsole.log(\"local mode: loopback only — use --remote for Tailscale access\");\n\t\t}\n\t\tif (args.https) {\n\t\t\tconsole.log(\"tls: native HTTPS enabled (cert + key hot-reload on file change)\");\n\t\t}\n\t});\n\thttpServer = server;\n\n\tconst shutdown = () => {\n\t\tconsole.log(\"shutting down…\");\n\t\tserver.close();\n\t\tpool.stopAll().finally(() => process.exit(0));\n\t};\n\tprocess.on(\"SIGINT\", shutdown);\n\tprocess.on(\"SIGTERM\", shutdown);\n}\n\n// Only run when executed directly (not when imported for the library exports).\nconst entryPath = process.argv[1];\nif (entryPath && import.meta.url === new URL(`file://${entryPath}`).href) {\n\tmain().catch((err) => {\n\t\tconsole.error(`fatal: ${err instanceof Error ? err.message : String(err)}`);\n\t\tprocess.exit(1);\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * dreb-dashboard — launch the dreb web dashboard server.\n *\n * Modes (exactly two, no LAN mode):\n * default loopback bind (127.0.0.1), no auth, no Tailscale needed\n * --remote requires Tailscale; binds all interfaces but every request\n * passes identity allowlist + pairing code + device cookies\n *\n * Usage:\n * dreb-dashboard [--port 5343] [--remote --allow me@example.com [--allow ...]]\n */\n\nimport { existsSync, readFileSync, watch } from \"node:fs\";\nimport { createServer as createHttpServer, type Server as HttpServer } from \"node:http\";\nimport { createServer as createHttpsServer, type Server as HttpsServer } from \"node:https\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { DashboardAuth } from \"./server/auth.js\";\nimport { FilePairingStorage, loadOrCreateDashboardSecret } from \"./server/pairing-storage.js\";\nimport { RuntimePool } from \"./server/runtime-pool.js\";\nimport { createDashboardServer } from \"./server/server.js\";\n\nexport { DashboardAuth, TailscaleStatusResolver } from \"./server/auth.js\";\nexport { EventHub } from \"./server/event-hub.js\";\nexport { canonicalizePath, FileApi } from \"./server/files.js\";\nexport { FilePairingStorage, loadOrCreateDashboardSecret } from \"./server/pairing-storage.js\";\nexport { RuntimePool, resolveDrebCliPath } from \"./server/runtime-pool.js\";\nexport { createDashboardServer, parseDeviceCookie } from \"./server/server.js\";\nexport type * from \"./shared/protocol.js\";\n\nconst DEFAULT_PORT = 5343;\n\ninterface CliArgs {\n\tport: number;\n\tremote: boolean;\n\tallow: string[];\n\thelp: boolean;\n\thttps: boolean;\n\tcert?: string;\n\tkey?: string;\n}\n\nexport function parseArgs(argv: string[]): CliArgs {\n\tconst args: CliArgs = { port: DEFAULT_PORT, remote: false, allow: [], help: false, https: false };\n\tfor (let i = 0; i < argv.length; i++) {\n\t\tconst arg = argv[i];\n\t\tif (arg === \"--port\") {\n\t\t\tconst value = argv[++i];\n\t\t\tconst port = Number.parseInt(value ?? \"\", 10);\n\t\t\tif (!Number.isInteger(port) || port < 1 || port > 65535) {\n\t\t\t\tthrow new Error(`Invalid --port value: ${value}`);\n\t\t\t}\n\t\t\targs.port = port;\n\t\t} else if (arg === \"--remote\") {\n\t\t\targs.remote = true;\n\t\t} else if (arg === \"--allow\") {\n\t\t\tconst value = argv[++i];\n\t\t\tif (!value) throw new Error(\"--allow requires an identity (Tailscale login name)\");\n\t\t\targs.allow.push(value);\n\t\t} else if (arg === \"--https\") {\n\t\t\targs.https = true;\n\t\t} else if (arg === \"--cert\") {\n\t\t\tconst value = argv[++i];\n\t\t\tif (!value) throw new Error(\"--cert requires a path to a PEM certificate file\");\n\t\t\targs.cert = value;\n\t\t} else if (arg === \"--key\") {\n\t\t\tconst value = argv[++i];\n\t\t\tif (!value) throw new Error(\"--key requires a path to a PEM private key file\");\n\t\t\targs.key = value;\n\t\t} else if (arg === \"--help\" || arg === \"-h\") {\n\t\t\targs.help = true;\n\t\t} else {\n\t\t\tthrow new Error(`Unknown argument: ${arg}`);\n\t\t}\n\t}\n\tif (args.remote && args.allow.length === 0) {\n\t\tthrow new Error(\"--remote requires at least one --allow <tailscale-login> (empty allowlist denies everyone)\");\n\t}\n\t// Native TLS. `https` is opt-in; it requires cert AND key (no silent\n\t// fallback to plain HTTP). The dashboard terminates TLS itself — no reverse\n\t// proxy, no auth-model change. `req.socket.remoteAddress` stays the real\n\t// tailnet IP, so identity resolution + allowlist + pairing keep working.\n\tif (args.https) {\n\t\tif (!args.cert) throw new Error(\"--https requires --cert <path>\");\n\t\tif (!args.key) throw new Error(\"--https requires --key <path>\");\n\t}\n\treturn args;\n}\n\n/**\n * Warning shown at startup when `--remote` binds the tailnet over plain HTTP\n * (no `--https`). Browsers treat plain HTTP over a non-loopback host as an\n * INSECURE context, so service workers and the Notification API are\n * unavailable — PWA install and mobile notifications (iOS exposes no\n * Notification API at all there) silently never work. Loopback (127.0.0.1) is\n * exempt (it counts as secure), so pure-local mode returns no warning.\n *\n * Returns the multi-line warning string, or `undefined` when TLS is enabled or\n * the server is loopback-only. Kept pure (no console) so it is unit-testable.\n */\nexport function insecureRemoteWarning(args: Pick<CliArgs, \"remote\" | \"https\">): string | undefined {\n\tif (!args.remote || args.https) return undefined;\n\treturn (\n\t\t\"⚠ warning: --remote without --https serves plain HTTP over the tailnet, which browsers treat as an\\n\" +\n\t\t\" INSECURE context — service workers, PWA install, and mobile notifications (especially iOS) will NOT work.\\n\" +\n\t\t\" Enable TLS for the tailnet hostname:\\n\" +\n\t\t\" tailscale cert <host>.<tailnet>.ts.net\\n\" +\n\t\t\" …then relaunch with --https --cert <host>.<tailnet>.ts.net.crt --key <host>.<tailnet>.ts.net.key\\n\" +\n\t\t\" and open the dashboard via https://<host>.<tailnet>.ts.net (the hostname, not the tailnet IP).\"\n\t);\n}\n\ntype ShutdownWatcher = Pick<ReturnType<typeof watch>, \"close\">;\n\ninterface ShutdownDeps {\n\tlog: (message: string) => void;\n\tclearReloadTimer: () => void;\n\twatchers: Iterable<ShutdownWatcher>;\n\tcloseServer: () => void;\n\tstopAll: () => Promise<unknown>;\n\texit: (code: number) => void;\n}\n\nexport function createShutdown(deps: ShutdownDeps): (message: string, exitCode: number) => void {\n\tlet shuttingDown = false;\n\treturn (message: string, exitCode: number) => {\n\t\tif (shuttingDown) return;\n\t\tshuttingDown = true;\n\t\tdeps.log(message);\n\t\tdeps.clearReloadTimer();\n\t\tfor (const watcher of deps.watchers) watcher.close();\n\t\tdeps.closeServer();\n\t\tvoid deps\n\t\t\t.stopAll()\n\t\t\t.catch((err) => {\n\t\t\t\tdeps.log(`shutdown teardown failed: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\t})\n\t\t\t.finally(() => deps.exit(exitCode));\n\t};\n}\n\ntype TlsOptions = { cert: string; key: string };\ntype TlsWatchFilename = string | Buffer | null;\ntype TlsWatchListener = (event: string, filename: TlsWatchFilename) => void;\ntype TlsFileWatcher = ShutdownWatcher & {\n\ton(event: \"error\", listener: (err: unknown) => void): unknown;\n};\n\ninterface TlsReloaderDeps {\n\treadTlsOptions: () => TlsOptions;\n\tsetSecureContext: (options: TlsOptions) => void;\n\tlog: (message: string) => void;\n\twarn: (message: string) => void;\n}\n\nexport function createTlsReloader(deps: TlsReloaderDeps): () => void {\n\treturn () => {\n\t\ttry {\n\t\t\tdeps.setSecureContext(deps.readTlsOptions());\n\t\t\tdeps.log(\"tls certificate reloaded\");\n\t\t} catch (err) {\n\t\t\tdeps.warn(`tls reload failed (keeping old cert): ${err instanceof Error ? err.message : String(err)}`);\n\t\t}\n\t};\n}\n\nexport function shouldReloadForFile(filename: TlsWatchFilename, certBase: string, keyBase: string): boolean {\n\tif (filename == null) return true;\n\tif (typeof filename !== \"string\") return false;\n\treturn filename === certBase || filename === keyBase;\n}\n\ninterface TlsWatchDeps {\n\tcertPath: string;\n\tkeyPath: string;\n\twatchDirectory: (directory: string, listener: TlsWatchListener) => TlsFileWatcher;\n\treloadTls: () => void;\n\twarn: (message: string) => void;\n\tdebounceMs?: number;\n}\n\ninterface TlsWatchController {\n\twatchers: TlsFileWatcher[];\n\tclearReloadTimer: () => void;\n}\n\nexport function createTlsWatchers(deps: TlsWatchDeps): TlsWatchController {\n\tlet reloadTimer: ReturnType<typeof setTimeout> | undefined;\n\tconst watchers: TlsFileWatcher[] = [];\n\tconst debounceMs = deps.debounceMs ?? 500;\n\tconst certBase = basename(deps.certPath);\n\tconst keyBase = basename(deps.keyPath);\n\tconst clearReloadTimer = () => {\n\t\tif (!reloadTimer) return;\n\t\tclearTimeout(reloadTimer);\n\t\treloadTimer = undefined;\n\t};\n\tconst scheduleReload = () => {\n\t\tclearReloadTimer();\n\t\treloadTimer = setTimeout(deps.reloadTls, debounceMs);\n\t};\n\n\tfor (const dir of new Set([dirname(deps.certPath), dirname(deps.keyPath)])) {\n\t\ttry {\n\t\t\tconst watcher = deps.watchDirectory(dir, (_event, filename) => {\n\t\t\t\t// `fs.watch` directory events on macOS (FSEvents) frequently fire\n\t\t\t\t// with `filename === null`; fall through and reload anyway.\n\t\t\t\t// On Linux (inotify) the basename is carried reliably and filters\n\t\t\t\t// out unrelated files in the parent directory.\n\t\t\t\tif (!shouldReloadForFile(filename, certBase, keyBase)) return;\n\t\t\t\tscheduleReload();\n\t\t\t});\n\t\t\twatcher.on(\"error\", (err) => {\n\t\t\t\tdeps.warn(`tls cert watch error: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\t});\n\t\t\twatchers.push(watcher);\n\t\t} catch (err) {\n\t\t\tdeps.warn(`tls cert watch setup failed for ${dir}: ${err instanceof Error ? err.message : String(err)}`);\n\t\t}\n\t}\n\n\treturn { watchers, clearReloadTimer };\n}\n\nconst HELP = `dreb-dashboard — dreb web dashboard server\n\nUsage: dreb-dashboard [options]\n\nOptions:\n --port <n> Port to listen on (default ${DEFAULT_PORT})\n --remote Enable remote mode (requires Tailscale). Without this\n flag the server binds 127.0.0.1 only — no LAN access.\n Plain HTTP over the tailnet is an INSECURE context: add\n --https (below) or mobile PWA install + notifications\n (especially iOS) will not work.\n --allow <identity> Tailscale login name allowed to pair (repeatable;\n required with --remote)\n --https Terminate TLS on the dashboard itself (native TLS). No\n reverse proxy, no auth-model change. Requires --cert\n and --key. Mainly for --remote (loopback is already a\n secure context): use 'tailscale cert' files for a\n tailnet hostname so mobile PWAs + notifications work.\n NOTE: with --https the server speaks TLS only, so the\n host's plain-http local tab (http://127.0.0.1) stops\n working — use the tailnet hostname (https://...) there.\n --cert <path> PEM certificate file (required with --https)\n --key <path> PEM private key file (required with --https)\n --help Show this help\n`;\n\nasync function main(): Promise<void> {\n\tlet args: CliArgs;\n\ttry {\n\t\targs = parseArgs(process.argv.slice(2));\n\t} catch (err) {\n\t\tconsole.error(`error: ${err instanceof Error ? err.message : String(err)}`);\n\t\tconsole.error(HELP);\n\t\tprocess.exit(1);\n\t}\n\tif (args.help) {\n\t\tconsole.log(HELP);\n\t\treturn;\n\t}\n\n\tconst agentDir = join(homedir(), \".dreb\", \"agent\");\n\tconst auth = new DashboardAuth({\n\t\tremoteEnabled: args.remote,\n\t\tallowedIdentities: args.allow,\n\t\tstorage: new FilePairingStorage(join(agentDir, \"dashboard-pairings.json\")),\n\t\tsecret: loadOrCreateDashboardSecret(join(agentDir, \"dashboard-auth-secret\")),\n\t\tlogger: (line) => console.warn(`[dashboard-auth] ${line}`),\n\t});\n\tconst pool = new RuntimePool();\n\n\t// Static client assets live next to the compiled server (dist/static).\n\tconst staticDir = join(dirname(fileURLToPath(import.meta.url)), \"static\");\n\n\t// The server's own build version (dist/index.js → ../package.json) — surfaced\n\t// in the settings footer so a stale long-running service is spottable.\n\tlet serverVersion: string | undefined;\n\ttry {\n\t\tconst pkgPath = join(dirname(fileURLToPath(import.meta.url)), \"..\", \"package.json\");\n\t\tserverVersion = JSON.parse(readFileSync(pkgPath, \"utf8\")).version;\n\t} catch {\n\t\tserverVersion = undefined;\n\t}\n\n\t// Restart hook: exit non-zero so a supervisor (systemd Restart=on-failure,\n\t// etc.) respawns the process with the freshly-built dist. Assigned the http\n\t// server below via a mutable holder so the closure can close it first.\n\t// The TLS hot-reload debounce timer and directory watchers are lifted here\n\t// too, so restart and signal shutdown can release them (otherwise they leak\n\t// across restart/exit).\n\tlet httpServer: HttpServer | HttpsServer | undefined;\n\tlet clearTlsReloadTimer = () => {};\n\tconst tlsWatchers: TlsFileWatcher[] = [];\n\tconst beginShutdown = createShutdown({\n\t\tlog: (message) => console.log(message),\n\t\tclearReloadTimer: () => clearTlsReloadTimer(),\n\t\twatchers: tlsWatchers,\n\t\tcloseServer: () => httpServer?.close(),\n\t\tstopAll: () => pool.stopAll(),\n\t\texit: (code) => process.exit(code),\n\t});\n\tconst onRestart = () => beginShutdown(\"restart requested — exiting for supervisor to respawn\", 1);\n\n\tconst { SessionManager } = await import(\"@dreb/coding-agent\");\n\tconst app = createDashboardServer({\n\t\tauth,\n\t\tpool,\n\t\tstaticDir: existsSync(staticDir) ? staticDir : undefined,\n\t\tserverVersion,\n\t\tonRestart,\n\t\tlistAllSessions: () => SessionManager.listAll(),\n\t\tdeleteSession: async (path: string) => {\n\t\t\tconst result = await SessionManager.deleteSession(path, {});\n\t\t\tif (!result.ok) throw new Error(result.error ?? \"Unknown deletion error\");\n\t\t\treturn { method: result.method };\n\t\t},\n\t});\n\n\tconst host = args.remote ? \"0.0.0.0\" : \"127.0.0.1\";\n\tconst scheme = args.https ? \"https\" : \"http\";\n\n\t// Native TLS: the dashboard terminates TLS itself (no reverse proxy). The\n\t// auth model is unchanged — `req.socket.remoteAddress` is still the real\n\t// tailnet IP, so Tailscale identity resolution + allowlist + pairing keep\n\t// working. Local mode never sets --https (loopback is already a secure\n\t// context), but it's allowed if the operator wants it.\n\tlet server: HttpServer | HttpsServer;\n\tif (args.https && args.cert && args.key) {\n\t\tconst readTlsOptions = () => ({\n\t\t\tcert: readFileSync(args.cert!, \"utf-8\"),\n\t\t\tkey: readFileSync(args.key!, \"utf-8\"),\n\t\t});\n\t\tlet tlsOptions: TlsOptions;\n\t\ttry {\n\t\t\ttlsOptions = readTlsOptions();\n\t\t} catch (err) {\n\t\t\tconsole.error(`error: failed to read TLS cert/key: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tconst httpsServer = createHttpsServer(tlsOptions, app);\n\t\t// Hot-reload the cert on file change (zero-downtime renewal — e.g. a\n\t\t// systemd timer rewrites the `tailscale cert` files daily). New\n\t\t// connections pick up the new cert; existing ones finish on the old.\n\t\tconst reloadTls = createTlsReloader({\n\t\t\treadTlsOptions,\n\t\t\tsetSecureContext: (options) => httpsServer.setSecureContext(options),\n\t\t\tlog: (message) => console.log(message),\n\t\t\twarn: (message) => console.warn(message),\n\t\t});\n\t\t// Debounce: cert + key are often rewritten in quick succession. Watch\n\t\t// the PARENT DIRECTORY (not each file) and filter by filename: `fs.watch`\n\t\t// follows the inode, so an atomic renewal (write temp + rename(2))\n\t\t// replaces the inode and a file-level watcher goes silent on the new\n\t\t// file after the first reload. A directory watcher survives renames and\n\t\t// reports the changed basename via the `filename` callback argument.\n\t\tconst tlsWatchController = createTlsWatchers({\n\t\t\tcertPath: args.cert,\n\t\t\tkeyPath: args.key,\n\t\t\twatchDirectory: (dir, listener) => watch(dir, listener),\n\t\t\treloadTls,\n\t\t\twarn: (message) => console.warn(message),\n\t\t});\n\t\tclearTlsReloadTimer = tlsWatchController.clearReloadTimer;\n\t\ttlsWatchers.push(...tlsWatchController.watchers);\n\t\tserver = httpsServer;\n\t} else {\n\t\tserver = createHttpServer(app);\n\t}\n\tserver.listen(args.port, host, () => {\n\t\tconsole.log(\n\t\t\t`dreb dashboard listening on ${scheme}://${host === \"0.0.0.0\" ? \"<tailscale-ip>\" : host}:${args.port}`,\n\t\t);\n\t\tif (args.remote) {\n\t\t\tconsole.log(`remote mode: allowed identities = ${args.allow.join(\", \")}`);\n\t\t\tconst { code, expiresInMs } = auth.currentPairingCode();\n\t\t\tconsole.log(\n\t\t\t\t`pairing code: ${code} (rotates every 30s; current code rolls in ${Math.ceil(expiresInMs / 1000)}s)`,\n\t\t\t);\n\t\t\tconsole.log(\"new devices enter this code; see it live in dashboard Settings on the host machine\");\n\t\t} else {\n\t\t\tconsole.log(\"local mode: loopback only — use --remote for Tailscale access\");\n\t\t}\n\t\tif (args.https) {\n\t\t\tconsole.log(\"tls: native HTTPS enabled (cert + key hot-reload on file change)\");\n\t\t} else {\n\t\t\tconst insecureWarning = insecureRemoteWarning(args);\n\t\t\tif (insecureWarning) console.warn(insecureWarning);\n\t\t}\n\t});\n\thttpServer = server;\n\n\tconst shutdown = () => beginShutdown(\"shutting down…\", 0);\n\tprocess.on(\"SIGINT\", shutdown);\n\tprocess.on(\"SIGTERM\", shutdown);\n}\n\n// Only run when executed directly (not when imported for the library exports).\nconst entryPath = process.argv[1];\nif (entryPath && import.meta.url === new URL(`file://${entryPath}`).href) {\n\tmain().catch((err) => {\n\t\tconsole.error(`fatal: ${err instanceof Error ? err.message : String(err)}`);\n\t\tprocess.exit(1);\n\t});\n}\n"]}
|
|
@@ -71,7 +71,7 @@ Please report this to https://github.com/markedjs/marked.`,e){let e=`<p>An error
|
|
|
71
71
|
`)),ve().length>0&&t.push([`Attached images included inline with this turn:`,...ve().map((e,t)=>`- image ${t+1}: ${e.fileName} (${Vc(e.size)}, ${e.mimeType})`)].join(`
|
|
72
72
|
`)),[e,...t].filter(e=>e.trim()).join(`
|
|
73
73
|
|
|
74
|
-
`)}async function Ot(){let t=r().trim();if(!t&&be().length===0&&ve().length===0)return;let n=Dt(t||`Please review the attached item(s). `);P(void 0);try{let t=ve(),r=t.length>0?await Promise.all(t.map(async({blob:e,mimeType:t})=>({data:await at(e),mimeType:t}))):void 0;Ue()?await X.prompt(e.sessionKey,n,a(),r):r?await X.prompt(e.sessionKey,n,void 0,r):await X.prompt(e.sessionKey,n),ps(e.sessionKey,n),Ce(void 0),i(``),Y(),xe([]),tt()}catch(e){P(e instanceof Error?e.message:String(e))}}async function kt(){c(!0);try{await X.abort(e.sessionKey),await ht(),await e.store.refreshFleet()}catch(e){P(e instanceof Error?e.message:String(e))}finally{c(!1)}}async function At(){if(!l()){u(!0),P(void 0);try{await X.stopRuntime(e.sessionKey),e.store.navigate({screen:`fleet`})}catch(e){P(e instanceof Error?e.message:String(e))}finally{u(!1)}}}async function jt(t){try{t===`compaction`?await X.abortCompaction(e.sessionKey):t===`retry`&&await X.abortRetry(e.sessionKey)}catch(e){P(e instanceof Error?e.message:String(e))}}function Mt(e){Me?.querySelectorAll(`details.tool`).forEach(t=>{t.open=e})}let Nt=()=>Object.values(t()?.backgroundAgents??{}).filter(e=>e.status===`running`),Pt=()=>Object.values(t()?.backgroundAgents??{}).filter(e=>e.status!==`running`),Ft=()=>t()?.tasks??[],It=()=>Ft().filter(e=>e.status===`completed`).length,Lt=()=>n()?.state.contextUsage??I()?.contextUsage,Rt=()=>typeof window.matchMedia==`function`&&window.matchMedia(`(max-width: 700px)`).matches,zt=()=>t()?.sessionName??n()?.state.sessionName,Bt=()=>zt()??t()?.title??e.sessionKey,Vt=()=>{let e=n()?.cwd;if(!e)return;let t=ae();return`${Lc(e)}${t?` (${t})`:``}`},Ht=()=>{let e=Vt(),t=zt();return e&&t?`${e} • ${t}`:e??t??`session`},Ut=()=>{let e=I()?.tokens;if(!e)return;let t=[];return e.input&&t.push(`↑${Ic(e.input)}`),e.output&&t.push(`↓${Ic(e.output)}`),e.cacheRead&&t.push(`R${Ic(e.cacheRead)}`),e.cacheWrite&&t.push(`W${Ic(e.cacheWrite)}`),t.length>0?t.join(` `):void 0},Wt=()=>{let e=I()?.cost??0,t=n()?.state.usingSubscription??!1;if(!e&&!t)return;let r=`$${e.toFixed(3)}${t?` (sub)`:``}`,i=se();return i!==void 0&&i>e&&(r+=`, today: $${i.toFixed(2)}`),r},Gt=()=>{let e=Lt();if(e)return`ctx ${e.percent===null?`?`:`${e.percent.toFixed(0)}%`}/${Ic(e.contextWindow)}`},Kt=()=>{let e=n()?.state.model;if(!e)return;let t=re()?.models.find(t=>t.provider===e.provider&&t.modelId===e.id);if(!(!t||t.count<3))return`${Math.round(t.median)} tok/s`},qt=()=>[Ut(),Wt(),Gt(),Kt()].filter(Boolean),Jt=()=>[...(ge().steeringMessages??ge().steering.map(e=>({text:e}))).map(e=>({kind:`steer`,text:e.images?.length?`${e.text} (${e.images.length} image(s))`:e.text})),...(ge().followUpMessages??ge().followUp.map(e=>({text:e}))).map(e=>({kind:`follow-up`,text:e.images?.length?`${e.text} (${e.images.length} image(s))`:e.text}))],Yt=()=>{let e=r();if(le()||!e.startsWith(`/`))return;let t=e.slice(1);if(!/\s/.test(t))return t.toLowerCase()},Xt=N(()=>{let e=Yt();return e===void 0?[]:ce().filter(t=>{let n=t.name.toLowerCase();return!e||n.startsWith(e)||n.includes(e)}).sort((t,n)=>!t.name.toLowerCase().startsWith(e)-+!n.name.toLowerCase().startsWith(e)||t.name.localeCompare(n.name)).slice(0,8)}),Zt=()=>Xt().length>0,Qt=e=>{i(`/${e.name} `),B(!0),queueMicrotask(()=>Pe?.focus())};return M(()=>{let e=Xt().length;ue()>=e&&de(Math.max(0,e-1))}),(()=>{var c=pc(),u=c.firstChild,k=u.firstChild,M=k.firstChild.nextSibling,N=M.nextSibling,F=u.nextSibling,te=F.firstChild,L=F.nextSibling,ne=L.firstChild,re=ne.firstChild;J(M,Bt),J(k,V(U,{get when(){return!h()},get children(){var e=Hs();return J(e,(()=>{var e=G(()=>!!n()?.cwd);return()=>e()?Lc(n().cwd):void 0})()),e}}),N),J(k,V(U,{get when(){return!h()},get children(){var t=Ws(),r=t.firstChild,i=r.firstChild.nextSibling.nextSibling,a=r.nextSibling;a.firstChild.nextSibling;var o=a.nextSibling;return r.$$click=()=>f(!0),J(i,()=>Pc(n()?.state.model)),a.$$click=async()=>{let t=n()?.state.thinkingLevel??`off`,r=jc[(jc.indexOf(t)+1)%jc.length];try{await X.setThinking(e.sessionKey,r),await e.store.refreshFleet()}catch(e){P(e instanceof Error?e.message:String(e))}},J(a,()=>n()?.state.thinkingLevel??`—`,null),J(t,V(U,{get when(){return Lt()},get children(){var e=Us();return e.firstChild.nextSibling,J(e,(()=>{var e=G(()=>Lt().percent===null);return()=>e()?`?`:`${Lt().percent.toFixed(0)}%`})(),null),e}}),o),o.$$click=()=>m(!p()),j(()=>q(r,`title`,Fc(n()?.state.model))),t}}),N),N.$$click=()=>g(!h()),J(N,()=>h()?`details ▾`:`details ▴`),J(u,V(U,{get when(){return!h()},get children(){return[(()=>{var e=Ks(),t=e.firstChild,n=t.nextSibling;return J(t,Ht),n.$$click=St,J(n,V(H,{get each(){return qt()},children:e=>(()=>{var t=mc();return J(t,e),t})()})),J(e,V(U,{get when(){return ke()},get children(){var e=Gs(),t=Re;return typeof t==`function`?Ie(t,e):Re=e,J(e,V(U,{get when(){return je()},get children(){var e=Es();return J(e,je),e}}),null),J(e,V(U,{get when(){return I()},get fallback(){return hc()},children:e=>(()=>{var t=gc(),n=t.firstChild.nextSibling,r=n.nextSibling.nextSibling,i=r.nextSibling.nextSibling,a=i.firstChild,o=i.nextSibling.nextSibling,s=o.firstChild,c=o.nextSibling.nextSibling,l=c.firstChild,u=c.nextSibling.nextSibling,d=u.nextSibling.nextSibling;return d.firstChild,J(n,()=>e().userMessages),J(r,()=>e().assistantMessages),J(i,()=>e().toolCalls,a),J(i,()=>e().toolResults,null),J(o,()=>Ic(e().tokens.input),s),J(o,()=>Ic(e().tokens.output),null),J(c,()=>Ic(e().tokens.cacheRead),l),J(c,()=>Ic(e().tokens.cacheWrite),null),J(u,()=>Ic(e().tokens.total)),J(d,()=>e().cost.toFixed(4),null),t})()}),null),e}}),null),e})(),V(U,{get when(){return p()},get children(){var t=Ys(),r=t.firstChild,i=r.nextSibling,a=i.nextSibling,o=a.nextSibling,s=o.nextSibling,c=s.nextSibling,u=c.nextSibling,d=u.nextSibling;return i.$$click=()=>S(!0),a.$$click=()=>Mt(!0),o.$$click=()=>Mt(!1),s.$$click=()=>w(!0),c.$$click=bt,u.$$click=et,J(t,V(U,{get when(){return Rt()},get children(){return[(()=>{var e=qs();return e.firstChild,e.$$click=()=>f(!0),J(e,()=>Pc(n()?.state.model),null),e})(),(()=>{var t=Js();return t.firstChild,t.$$click=async()=>{let t=n()?.state.thinkingLevel??`off`,r=jc[(jc.indexOf(t)+1)%jc.length];try{await X.setThinking(e.sessionKey,r),await e.store.refreshFleet()}catch(e){P(e instanceof Error?e.message:String(e))}},J(t,()=>n()?.state.thinkingLevel??`—`,null),t})()]}}),d),d.$$click=At,J(d,()=>l()?`stopping runtime…`:`stop runtime`),j(t=>{var n=X.exportHtmlUrl(e.sessionKey),i=l();return n!==t.e&&q(r,`href`,t.e=n),i!==t.t&&(d.disabled=t.t=i),t},{e:void 0,t:void 0}),t}})]}}),null),J(c,V(U,{get when(){return G(()=>!!(!h()&&n()?.state.modelFallbackMessage))()&&!D()},get children(){var e=Xs(),t=e.firstChild.firstChild;t.firstChild;var r=t.nextSibling;return J(t,()=>n().state.modelFallbackMessage,null),r.$$click=()=>O(!0),e}}),F),F.addEventListener(`scroll`,()=>{ze=Je()}),F.$$touchend=()=>{Be=!1,ze=Je()},F.$$touchstart=()=>{Be=!0},F.addEventListener(`wheel`,()=>{Be=!0,Xe()});var ie=Me;return typeof ie==`function`?Ie(ie,F):Me=F,J(te,V(U,{get when(){return t()},get fallback(){return _c()},get children(){return[V(H,{get each(){return t().widgets.above},children:e=>(()=>{var t=vc();return J(t,e),t})()}),V(os,{get entries(){return t().entries},get resetKey(){return e.sessionKey}}),V(H,{get each(){return t().widgets.below},children:e=>(()=>{var t=vc();return J(t,e),t})()})]}})),re.$$click=()=>v(!_()),J(re,()=>_()?`compose ▴`:`compose ▾`),J(ne,V(U,{get when(){return _()},get children(){var e=Zs();return J(e,(()=>{var e=G(()=>!!qe());return()=>e()?`agent working — open controls to stop or steer`:G(()=>Jt().length>0)()?`${Jt().length} queued message(s)`:`composer hidden for transcript reading`})()),e}}),null),J(L,V(U,{get when(){return!_()},get children(){var n=uc(),c=n.firstChild,l=c.firstChild,u=l.nextSibling,d=u.firstChild,f=d.nextSibling,p=f.nextSibling,m=p.nextSibling,h=m.nextSibling;J(n,V(U,{get when(){return Ft().length>0},get children(){var e=Qs(),t=e.firstChild,n=t.firstChild.nextSibling,r=n.nextSibling.nextSibling;r.nextSibling;var i=t.nextSibling;return J(t,It,n),J(t,()=>Ft().length,r),J(i,V(H,{get each(){return Ft()},children:e=>(()=>{var t=yc(),n=t.firstChild;return J(t,(()=>{var t=G(()=>e.status===`completed`);return()=>t()?`☑`:e.status===`in_progress`?`⧖`:`☐`})(),n),J(t,()=>e.title,null),j(n=>{var r=e.status===`completed`,i=e.status===`in_progress`;return r!==n.e&&t.classList.toggle(`done`,n.e=r),i!==n.t&&t.classList.toggle(`active`,n.t=i),n},{e:void 0,t:void 0}),t})()})),j(()=>e.open=!Rt()),e}}),c),J(n,V(U,{get when(){return Nt().length+Pt().length>0},get children(){var t=ec(),n=t.firstChild,r=n.nextSibling,i=r.firstChild.nextSibling,a=i.nextSibling.nextSibling;return a.nextSibling,n.$$click=()=>b(!y()),J(n,()=>y()?`subagents ▴`:`subagents ▾`),J(r,()=>Nt().length,i),J(r,()=>Pt().length,a),J(t,V(U,{get when(){return y()},get children(){return $s()}}),null),J(t,V(U,{get when(){return!y()},get children(){return V(H,{get each(){return[...Nt(),...Pt()].slice(0,4)},children:t=>(()=>{var n=bc(),r=n.firstChild,i=r.nextSibling,a=i.firstChild;return n.$$click=()=>e.store.navigate({screen:`subagent`,key:e.sessionKey,agentId:t.agentId}),J(r,(()=>{var e=G(()=>t.status===`running`);return()=>e()?`●`:t.status===`completed`?`✓`:`✕`})()),J(i,()=>t.agentType,a),J(i,()=>t.taskSummary,null),j(()=>Ne(r,t.status===`running`?`live`:`done`)),n})()})}}),null),j(e=>{var r=!!y(),i=y()?`show subagents`:`hide subagents`;return r!==e.e&&t.classList.toggle(`collapsed`,e.e=r),i!==e.t&&q(n,`title`,e.t=i),e},{e:void 0,t:void 0}),t}}),c),J(n,V(U,{get when(){return qe()||(t()?.statusEntries.length??0)>0||A()},get children(){var e=ic();return J(e,V(U,{get when(){return Ue()},get children(){var e=tc();return e.firstChild,J(e,(()=>{var e=G(()=>!!t()?.workingText);return()=>e()?` — ${t().workingText}`:``})(),null),J(e,(()=>{var e=G(()=>ee()>2);return()=>e()?` (${ee()}s)`:``})(),null),e}}),null),J(e,V(H,{get each(){return t()?.statusEntries??[]},children:e=>(()=>{var t=mc();return J(t,()=>e.text,null),J(t,V(U,{get when(){return e.key===`compaction`||e.key===`retry`},get children(){var t=xc();return t.$$click=()=>jt(e.key),t}}),null),j(()=>Ne(t,e.tone===`info`?`queued`:e.tone)),t})()}),null),J(e,V(U,{get when(){return A()},get children(){var e=nc();return J(e,A),e}}),null),J(e,V(U,{get when(){return qe()},get children(){var e=rc();return e.$$click=kt,J(e,()=>s()?`stopping…`:`■ stop`),j(()=>e.disabled=s()),e}}),null),e}}),c),J(c,V(U,{get when(){return Jt().length>0},get children(){var e=ac(),t=e.firstChild;return J(e,V(H,{get each(){return Jt()},children:e=>(()=>{var t=Sc(),n=t.firstChild;return J(n,()=>e.kind),J(t,()=>e.text,null),j(()=>q(t,`title`,e.text)),t})()}),t),t.$$click=ht,e}}),l),J(c,V(U,{get when(){return be().length>0||ve().length>0},get children(){var e=oc();return J(e,V(H,{get each(){return be()},children:(e,t)=>(()=>{var n=Cc(),r=n.firstChild;r.firstChild;var i=r.nextSibling,a=i.nextSibling;return J(r,()=>e.fileName,null),J(i,()=>Vc(e.size)),a.$$click=()=>xe(e=>e.filter((e,n)=>n!==t())),j(()=>q(n,`title`,e.path)),n})()}),null),J(e,V(H,{get each(){return ve()},children:(e,t)=>(()=>{var n=wc(),r=n.firstChild,i=r.nextSibling;return i.$$click=()=>st(t()),j(t=>{var i=`${e.fileName} (${Vc(e.size)})`,a=e.previewUrl,o=e.fileName;return i!==t.e&&q(n,`title`,t.e=i),a!==t.t&&q(r,`src`,t.t=a),o!==t.a&&q(r,`alt`,t.a=o),t},{e:void 0,t:void 0,a:void 0}),n})()}),null),e}}),l),J(c,V(U,{get when(){return Zt()},get children(){var e=sc();return J(e,V(H,{get each(){return Xt()},children:(e,t)=>(()=>{var n=Ec(),r=n.firstChild;r.firstChild;var i=r.nextSibling;return n.$$click=()=>Qt(e),n.addEventListener(`mouseenter`,()=>de(t())),J(r,()=>e.name,null),J(n,V(U,{get when(){return e.description},get children(){var t=Tc();return J(t,()=>e.description),t}}),i),J(i,()=>e.source),j(e=>{var r=`command-option-${t()}`,i=ue()===t(),a=ue()===t();return r!==e.e&&q(n,`id`,e.e=r),i!==e.t&&q(n,`aria-selected`,e.t=i),a!==e.a&&n.classList.toggle(`selected`,e.a=a),e},{e:void 0,t:void 0,a:void 0}),n})()})),e}}),l),l.$$keydown=t=>{if(Zt()){if(t.key===`ArrowDown`){t.preventDefault(),de((ue()+1)%Xt().length);return}if(t.key===`ArrowUp`){t.preventDefault(),de((ue()-1+Xt().length)%Xt().length);return}if(t.key===`Tab`||t.key===`Enter`&&!t.shiftKey&&!Rt()){t.preventDefault();let e=Xt()[ue()];e&&Qt(e);return}if(t.key===`Escape`){t.preventDefault(),B(!0);return}}if((t.key===`ArrowUp`||t.key===`ArrowDown`)&&(r()===``||Se()!==void 0)){let n=fs(e.sessionKey);if(n.length>0){if(t.preventDefault(),t.key===`ArrowUp`){let e=Se()===void 0?n.length-1:Math.max(0,Se()-1);Ce(e),i(n[e]??``)}else if(Se()!==void 0){let e=Se()+1;e>=n.length?(Ce(void 0),i(``)):(Ce(e),i(n[e]??``))}}return}t.key===`Enter`&&!t.shiftKey&&!Rt()&&(t.preventDefault(),Ot())},l.$$input=e=>{B(!1),de(0),Ce(void 0),i(e.currentTarget.value),Wc(e.currentTarget)},l.addEventListener(`paste`,e=>{let t=[...e.clipboardData?.items??[]].filter(e=>e.type.startsWith(`image/`)).map(e=>e.getAsFile()).filter(e=>!!e);t.length>0&&(e.preventDefault(),_t(t))});var g=Pe;typeof g==`function`?Ie(g,l):Pe=l,d.addEventListener(`change`,e=>{yt(e.currentTarget.files??[]),e.currentTarget.value=``});var _=Fe;typeof _==`function`?Ie(_,d):Fe=d,f.addEventListener(`change`,e=>{_t(e.currentTarget.files??[]),e.currentTarget.value=``});var v=Le;return typeof v==`function`?Ie(v,f):Le=f,p.$$click=()=>Fe?.click(),m.$$click=()=>Le?.click(),J(u,V(U,{get when(){return Ue()},get children(){var e=cc(),t=e.firstChild,n=t.nextSibling;return t.$$click=()=>o(`steer`),n.$$click=()=>o(`follow_up`),j(e=>{var r=a()===`steer`,i=a()===`follow_up`;return r!==e.e&&t.classList.toggle(`selected`,e.e=r),i!==e.t&&n.classList.toggle(`selected`,e.t=i),e},{e:void 0,t:void 0}),e}}),h),J(u,V(U,{get when(){return t()?.suggestedCommand},get children(){var e=lc(),n=e.firstChild.nextSibling;return e.$$click=()=>i(t().suggestedCommand),J(n,()=>t().suggestedCommand),e}}),h),h.$$click=Ot,j(e=>{var t=Ue()?`Message dreb — sends as steer while it works…`:`Message dreb…`,n=Zt()?`command-listbox`:void 0,r=Zt()?`command-option-${ue()}`:void 0;return t!==e.e&&q(l,`placeholder`,e.e=t),n!==e.t&&q(l,`aria-controls`,e.t=n),r!==e.a&&q(l,`aria-activedescendant`,e.a=r),e},{e:void 0,t:void 0,a:void 0}),j(()=>l.value=r()),n}}),null),J(c,V(U,{get when(){return t()?.uiRequests[0]},children:t=>V(Gc,{get request(){return t()},onRespond:async t=>{try{await X.extensionUiResponse(e.sessionKey,t)}catch(e){P(e instanceof Error?e.message:String(e))}}})}),null),J(c,V(U,{get when(){return d()},get children(){return V(qc,{get sessionKey(){return e.sessionKey},get state(){return n()?.state},onClose:()=>f(!1),onSelected:()=>e.store.refreshFleet()})}}),null),J(c,V(U,{get when(){return x()},get children(){return V(nt,{title:`compact context`,onDismiss:()=>S(!1),get actions(){return[(()=>{var e=ys();return e.$$click=()=>S(!1),e})(),(()=>{var t=Dc();return t.$$click=async()=>{S(!1);try{await X.compact(e.sessionKey)}catch(e){P(e instanceof Error?e.message:String(e))}},t})()]},get children(){return dc()}})}}),null),J(c,V(U,{get when(){return T()},get children(){return V(Kc,{get resources(){return fe()},get error(){return me()},onClose:()=>E(!1)})}}),null),J(c,V(U,{get when(){return we()},get children(){return V(nt,{title:`fork from message`,onDismiss:()=>Te(!1),get children(){return[V(U,{get when(){return Oe()},get children(){var e=Es();return J(e,Oe),e}}),V(U,{get when(){return Ee().length>0},get fallback(){return Oc()},get children(){var e=fc();return J(e,V(H,{get each(){return Ee()},children:e=>(()=>{var t=kc(),n=t.firstChild,r=n.nextSibling;return t.$$click=()=>xt(e.entryId),J(n,()=>e.entryId),J(r,()=>e.text),t})()})),e}})]}})}}),null),J(c,V(U,{get when(){return C()},get children(){return V(Yc,{get current(){return zt()??``},onClose:()=>w(!1),onRename:async t=>{try{await X.rename(e.sessionKey,t),await e.store.refreshFleet(),w(!1)}catch(e){P(e instanceof Error?e.message:String(e))}}})}}),null),j(e=>{var t=!!h(),n=h()?`show session details`:`hide session details`,r=!!_(),i=_()?`show composer and controls`:`hide composer and controls`;return t!==e.e&&u.classList.toggle(`collapsed`,e.e=t),n!==e.t&&q(N,`title`,e.t=n),r!==e.a&&L.classList.toggle(`collapsed`,e.a=r),i!==e.o&&q(re,`title`,e.o=i),e},{e:void 0,t:void 0,a:void 0,o:void 0}),c})()}function Yc(e){let[t,n]=k(e.current);return V(nt,{title:`rename session`,get onDismiss(){return e.onClose},get actions(){return[(()=>{var t=ys();return Pe(t,`click`,e.onClose,!0),t})(),(()=>{var n=Ac();return n.$$click=()=>e.onRename(t().trim()),j(()=>n.disabled=!t().trim()),n})()]},get children(){var e=_s(),r=e.firstChild;return r.$$input=e=>n(e.currentTarget.value),j(()=>r.value=t()),e}})}Me([`input`,`click`,`touchstart`,`touchend`,`keydown`]);var Xc=K(`<select><option value=on>on</option><option value=off>off`),Zc=K(`<div class=field style=margin-bottom:8px><input type=text placeholder="search models…">`),Qc=K(`<div class=model-list style=max-height:320px>`),$c=K(`<p class="muted small">No matching models.`),el=K(`<section class=model-provider-group><div class=model-provider-heading>`),tl=K(`<span class=model-name>`),nl=K(`<span class=model-reasoning>think`),rl=K(`<button type=button class=model-row><span class=model-current></span><span class=model-id></span><span class=model-provider-badge>`),il=K(`<div class=settings-error>`),al=K(`<div class=settings-warning>`),ol=K(`<p class="muted small"style=margin-bottom:16px>✓ saved`),sl=K(`<div class=setting-row><span class=setting-label><span class=name>pairing code</span><span class=hint>new devices enter this in the pairing screen; it rotates every 30s</span></span><span class=setting-control><code style=font-size:var(--fs-h2);letter-spacing:0.08em>`),cl=K(`<p class="muted small"style=padding-top:8px>No remote devices paired. Launch with <code>--remote --allow <identity></code> to enable Tailscale access.`),ll=K(`<p>This exits the dashboard server process and terminates <strong>all running sessions</strong>.`),ul=K(`<div class=screen-fill><main class="container settings-wrap"><h1>settings</h1><p class=settings-intro>Defaults for new sessions. Live sessions keep their current values — change those from the session view. Writes go to the global settings file on the host.</p><section class=settings-section><h2>dashboard</h2><div class=setting-row><span class=setting-label><span class=name>always expand thinking</span><span class=hint>this browser only — stored in localStorage, not the host settings file</span></span><span class=setting-control><label class=checkbox-control><input id=pref-expand-thinking type=checkbox><span>open by default</span></label></span></div><div class=setting-row><span class=setting-label><span class=name>auto-expand tool cards</span><span class=hint>this browser only — stored in localStorage, not the host settings file</span></span><span class=setting-control style=display:grid;gap:6px></span></div><div class=setting-row><span class=setting-label><span class=name>needs-attention notifications</span><span class=hint></span></span><span class=setting-control><label class=checkbox-control><input id=pref-notifications type=checkbox><span></span></label></span></div><div class=setting-row><span class=setting-label><span class=name>restart dashboard service</span><span class=hint></span></span><span class=setting-control><button type=button class="btn btn-small btn-danger"></button></span></div></section><section class=settings-section><h2>devices</h2><div class=device-row><span>this machine <span class=this-device></span></span><span class=meta>local · always allowed</span></div></section><footer>dreb<!> · dashboard`),dl=K(`<div>`),fl=K(`<p class=muted>`),pl=K(`<section class=settings-section><h2>model</h2><div class=setting-row><span class=setting-label><span class=name>default model</span><span class=hint>used by new sessions; validated against configured providers</span></span><span class=setting-control><button type=button class="btn btn-small model-picker-button"></button></span></div><div class=setting-row><span class=setting-label><span class=name>default thinking level</span><span class=hint></span></span><span class=setting-control><select></select></span></div><div class=setting-row><span class=setting-label><span class=name>transport</span><span class=hint>preferred model transport for new sessions</span></span><span class=setting-control><select>`),ml=K(`<div class="setting-row agent-context-row"><span class=setting-label><span class=name>agent definition context</span><span class=hint>choose a project to include its .dreb/agents definitions</span></span><span class=setting-control><select><option value>global/home only`),hl=K(`<section class=settings-section><h2>agent models</h2><p class="muted small"style=margin-bottom:8px>Per-agent fallback lists. First available model wins; empty lists revert to the default model. Agent definitions are loaded from an explicit project context so project-local agents do not depend on which runtime opened first.`),gl=K(`<section class=settings-section><h2>images</h2><div class=setting-row><span class=setting-label><span class=name>auto-resize images</span><span class=hint>resize image inputs before sending them to providers</span></span><span class=setting-control></span></div><div class=setting-row><span class=setting-label><span class=name>block images</span><span class=hint>prevent image inputs from being sent to providers</span></span><span class=setting-control>`),_l=K(`<section class=settings-section><h2>queueing</h2><div class=setting-row><span class=setting-label><span class=name>steering delivery</span><span class=hint>deliver queued steers all at once, or one per turn</span></span><span class=setting-control><select></select></span></div><div class=setting-row><span class=setting-label><span class=name>follow-up delivery</span><span class=hint>deliver queued follow-ups all at once, or one per turn</span></span><span class=setting-control><select>`),vl=K(`<section class=settings-section><h2>behavior</h2><div class=setting-row><span class=setting-label><span class=name>skill slash commands</span><span class=hint>register skills as slash commands in new sessions</span></span><span class=setting-control></span></div><div class=setting-row><span class=setting-label><span class=name>auto-load nested context</span><span class=hint>load nested AGENTS.md/CLAUDE.md when tools enter subdirectories</span></span><span class=setting-control></span></div><div class=setting-row><span class=setting-label><span class=name>hide thinking blocks</span><span class=hint>hide raw thinking blocks in rendered transcripts</span></span><span class=setting-control>`),yl=K(`<section class=settings-section><h2>reliability</h2><div class=setting-row><span class=setting-label><span class=name>auto-compaction</span><span class=hint>summarize old context when the window fills</span></span><span class=setting-control></span></div><div class=setting-row><span class=setting-label><span class=name>auto-retry</span><span class=hint>retry transient stream errors (rate limits, 5xx)</span></span><span class=setting-control>`),bl=K(`<p class="muted small settings-footnote">TUI-only settings (theme, cursor, editor) are managed in the terminal /settings menu.`),xl=K(`<option>`),Sl=K(`<p class="muted small">No agent definitions found.`),Cl=K(`<div class=agent-model-editor><button type=button class="btn btn-small">add model…`),wl=K(`<div class=agent-model-row><div class=agent-model-summary><span class=agent-model-name></span><span class=agent-model-description></span></div><div class=agent-model-fallbacks></div><button type=button class="btn btn-small agent-model-edit">`),Tl=K(`<span class="muted small">default`),El=K(`<span class=agent-model-chip>. `),Dl=K(`<p class="muted small">Using the default model.`),Ol=K(`<div class=agent-model-entry><span></span><div class=agent-model-entry-actions><button type=button class="btn btn-small">↑</button><button type=button class="btn btn-small">↓</button><button type=button class="btn btn-small">×`),kl=K(`<label class=checkbox-control><input type=checkbox><span>`),Al=K(`<div class=device-row><span></span><span class=meta> · paired </span><span class=actions><button type=button class="btn btn-small btn-danger">unpair`),jl=K(`<button type=button class="btn btn-small">cancel`),Ml=K(`<button type=button class="btn btn-small btn-danger">restart`),Nl=[`off`,`minimal`,`low`,`medium`,`high`,`xhigh`],Pl=[`all`,`one-at-a-time`],Fl=[`sse`,`websocket`,`auto`];function Il(e){return`${e.provider}/${e.id}`}function Ll(e){let t=Il(e);return e.name?`${t} — ${e.name}`:t}function Rl(e){return e.defaultProvider&&e.defaultModel?`${e.defaultProvider}/${e.defaultModel}`:`choose model…`}function zl(e,t){return`${e.provider}/${e.id} ${e.name??``}`.toLowerCase().includes(t)}function Bl(e){let t=new Map;for(let n of e){let e=t.get(n.provider)??[];e.push(n),t.set(n.provider,e)}return[...t.entries()].map(([e,t])=>({provider:e,models:t}))}function Vl(e,t,n){let r=t+n;if(r<0||r>=e.length)return e;let i=[...e];return[i[t],i[r]]=[i[r],i[t]],i}function Hl(e){return(()=>{var t=Xc();return t.addEventListener(`change`,t=>e.onChange(t.currentTarget.value===`on`)),j(()=>t.value=e.value?`on`:`off`),t})()}function Ul(e){let[t,n]=k(``),r=()=>new Set(e.selected??[]),i=N(()=>{let n=t().toLowerCase();return Bl(e.models.filter(e=>!n||zl(e,n)).slice(0,100))}),a=e=>r().has(Il(e));return V(nt,{get title(){return e.title},get onDismiss(){return e.onClose},class:`model-picker-modal`,get children(){return[(()=>{var e=Zc(),r=e.firstChild;return r.$$input=e=>n(e.currentTarget.value),j(()=>r.value=t()),e})(),(()=>{var t=Qc();return J(t,V(U,{get when(){return i().length>0},get fallback(){return $c()},get children(){return V(H,{get each(){return i()},children:t=>(()=>{var n=el(),r=n.firstChild;return J(r,()=>t.provider),J(n,V(H,{get each(){return t.models},children:t=>(()=>{var n=rl(),r=n.firstChild,i=r.nextSibling,o=i.nextSibling;return n.$$click=()=>e.onPick(t),J(r,()=>a(t)?`✓`:``),J(i,()=>t.id),J(n,V(U,{get when(){return t.name},get children(){var e=tl();return J(e,()=>t.name),e}}),o),J(o,()=>t.provider),J(n,V(U,{get when(){return t.reasoning},get children(){return nl()}}),null),j(e=>{var r=!!a(t),i=Ll(t);return r!==e.e&&n.classList.toggle(`current`,e.e=r),i!==e.t&&q(n,`title`,e.t=i),e},{e:void 0,t:void 0}),n})()}),null),n})()})}})),t})()]}})}function Wl(e){let[t,n]=k(),[r,i]=k([]),[a,o]=k(!1),[s,c]=k(),[l,u]=k(),[d,f]=k(),[p,m]=k(()=>{if(typeof Notification>`u`){let e=/iPad|iPhone|iPod/.test(navigator.userAgent),t=navigator.standalone===!0||window.matchMedia?.(`(display-mode: standalone)`).matches===!0;return e&&!t?`ios-install`:`unsupported`}return Notification.permission}),[h,{mutate:g,refetch:_}]=ee(async()=>{n(void 0);try{return await X.settings()}catch(e){n(e instanceof Error?e.message:String(e));return}}),[v]=ee(h,async()=>{try{let{models:e}=await X.settingsModels();return e}catch(e){return n(e instanceof Error?e.message:String(e)),[]}}),y=N(()=>{let t=new Set;for(let n of e.store.fleet().runtimes)t.add(n.cwd);for(let n of e.store.fleet().diskSessions)t.add(n.cwd);return[...t].sort((e,t)=>e.localeCompare(t))}),[b]=ee(()=>({settings:h(),cwd:d()}),async({cwd:e})=>{if(!h())return[];try{let{agentTypes:t}=await X.agentTypes(e);return t}catch(e){return n(e instanceof Error?e.message:String(e)),[]}}),[x,{refetch:S}]=ee(async()=>{let{devices:e}=await X.devices();return e}),[C,w]=k(),T;function E(){T&&clearTimeout(T),T=void 0}function D(e){E();let t=Math.max(250,e??3e4)+100;T=setTimeout(()=>void O(),t)}async function O(){try{let e=await X.pairingCode();if(!e.enabled||!e.code){w(void 0),E();return}w(e),D(e.expiresInMs)}catch(e){console.warn(`pairing code unavailable`,e),w(void 0),E()}}let[A]=ee(async()=>{try{let{version:e}=await X.version();return e}catch{return}}),[M]=ee(async()=>{try{return await X.serverInfo()}catch{return}}),[P,F]=k(!1),[I,ne]=k(!1),[re,ie]=k();async function ae(){ie(void 0),ne(!0);try{await X.restartServer()}catch(e){ie(e instanceof Error?e.message:String(e)),ne(!1)}}async function oe(e){n(void 0),i([]),o(!1);try{let t=await X.saveSettings(e);g(t),i(t.warnings??[]),o(!0),setTimeout(()=>o(!1),2e3)}catch(e){n(e instanceof Error?e.message:String(e)),await _()}}async function se(e,t){await oe({agentModels:{[e]:t}})}function R(e){return h()?.agentModels?.[e]??[]}async function ce(){typeof Notification>`u`||m(await Notification.requestPermission())}te(()=>void O()),L(E);let z=()=>e.store.auth();return(()=>{var n=ul(),i=n.firstChild,o=i.firstChild.nextSibling.nextSibling,m=o.firstChild.nextSibling,g=m.firstChild.nextSibling.firstChild.firstChild,_=m.nextSibling,w=_.firstChild.nextSibling,T=_.nextSibling,E=T.firstChild,D=E.firstChild.nextSibling,O=E.nextSibling.firstChild.firstChild,k=O.nextSibling,N=T.nextSibling.firstChild,ee=N.firstChild.nextSibling,te=N.nextSibling.firstChild,L=o.nextSibling,ne=L.firstChild.nextSibling.firstChild.firstChild.nextSibling,ie=L.nextSibling,le=ie.firstChild.nextSibling;return le.nextSibling,J(n,V(tt,{get store(){return e.store},active:`settings`}),i),J(i,V(U,{get when(){return t()},get children(){var e=il();return J(e,t),e}}),o),J(i,V(U,{get when(){return r().length>0},get children(){var e=al();return J(e,V(H,{get each(){return r()},children:e=>(()=>{var t=dl();return J(t,e),t})()})),e}}),o),J(i,V(U,{get when(){return a()},get children(){return ol()}}),o),J(i,V(U,{get when(){return h()},get fallback(){return(()=>{var e=fl();return J(e,()=>t()?`Settings could not be loaded — see the error above.`:`Loading settings…`),e})()},children:e=>[(()=>{var t=pl(),n=t.firstChild.nextSibling,r=n.firstChild.nextSibling.firstChild,i=n.nextSibling,a=i.firstChild,o=a.firstChild.nextSibling,s=a.nextSibling.firstChild,l=i.nextSibling.firstChild.nextSibling.firstChild;return r.$$click=()=>c({kind:`default`}),J(r,()=>Rl(e())),J(o,()=>Nl.join(` · `)),s.addEventListener(`change`,e=>oe({defaultThinkingLevel:e.currentTarget.value})),J(s,V(H,{each:Nl,children:e=>(()=>{var t=xl();return t.value=e,J(t,e),t})()})),l.addEventListener(`change`,e=>oe({transport:e.currentTarget.value})),J(l,V(H,{get each(){return[...Fl]},children:e=>(()=>{var t=xl();return t.value=e,J(t,e),t})()})),j(()=>s.value=e().defaultThinkingLevel??`off`),j(()=>l.value=e().transport??`sse`),t})(),(()=>{var t=hl();return t.firstChild.nextSibling,J(t,V(U,{get when(){return y().length>0},get children(){var e=ml(),t=e.firstChild.nextSibling.firstChild;return t.firstChild,t.addEventListener(`change`,e=>f(e.currentTarget.value||void 0)),J(t,V(H,{get each(){return y()},children:e=>(()=>{var t=xl();return t.value=e,J(t,e),t})()}),null),j(()=>t.value=d()??``),e}}),null),J(t,V(U,{get when(){return(b()??[]).length>0},get fallback(){return Sl()},get children(){return V(H,{get each(){return b()??[]},children:t=>{let n=()=>e().agentModels?.[t.name]??[];return(()=>{var e=wl(),r=e.firstChild,i=r.firstChild,a=i.nextSibling,o=r.nextSibling,s=o.nextSibling;return J(i,()=>t.name),J(a,()=>t.description),J(o,V(U,{get when(){return n().length>0},get fallback(){return Tl()},get children(){return V(H,{get each(){return n()},children:(e,t)=>(()=>{var n=El(),r=n.firstChild;return J(n,()=>t()+1,r),J(n,e,null),n})()})}})),s.$$click=()=>u(l()===t.name?void 0:t.name),J(s,()=>l()===t.name?`done`:`edit`),J(e,V(U,{get when(){return l()===t.name},get children(){var e=Cl(),r=e.firstChild;return J(e,V(U,{get when(){return n().length>0},get fallback(){return Dl()},get children(){return V(H,{get each(){return n()},children:(e,r)=>(()=>{var i=Ol(),a=i.firstChild,o=a.nextSibling.firstChild,s=o.nextSibling,c=s.nextSibling;return J(a,e),o.$$click=()=>void se(t.name,Vl(n(),r(),-1)),s.$$click=()=>void se(t.name,Vl(n(),r(),1)),c.$$click=()=>void se(t.name,n().filter((e,t)=>t!==r())),j(e=>{var t=r()===0,i=r()===n().length-1;return t!==e.e&&(o.disabled=e.e=t),i!==e.t&&(s.disabled=e.t=i),e},{e:void 0,t:void 0}),i})()})}}),r),r.$$click=()=>c({kind:`agent`,agentName:t.name}),e}}),null),e})()}})}}),null),t})(),(()=>{var t=gl(),n=t.firstChild.nextSibling,r=n.firstChild.nextSibling,i=n.nextSibling.firstChild.nextSibling;return J(r,V(Hl,{get value(){return e().imageAutoResize!==!1},onChange:e=>oe({imageAutoResize:e})})),J(i,V(Hl,{get value(){return e().blockImages===!0},onChange:e=>oe({blockImages:e})})),t})(),(()=>{var t=_l(),n=t.firstChild.nextSibling,r=n.firstChild.nextSibling.firstChild,i=n.nextSibling.firstChild.nextSibling.firstChild;return r.addEventListener(`change`,e=>oe({steeringMode:e.currentTarget.value})),J(r,V(H,{get each(){return[...Pl]},children:e=>(()=>{var t=xl();return t.value=e,J(t,e),t})()})),i.addEventListener(`change`,e=>oe({followUpMode:e.currentTarget.value})),J(i,V(H,{get each(){return[...Pl]},children:e=>(()=>{var t=xl();return t.value=e,J(t,e),t})()})),j(()=>r.value=e().steeringMode??`all`),j(()=>i.value=e().followUpMode??`all`),t})(),(()=>{var t=vl(),n=t.firstChild.nextSibling,r=n.firstChild.nextSibling,i=n.nextSibling,a=i.firstChild.nextSibling,o=i.nextSibling.firstChild.nextSibling;return J(r,V(Hl,{get value(){return e().enableSkillCommands!==!1},onChange:e=>oe({enableSkillCommands:e})})),J(a,V(Hl,{get value(){return e().autoLoadNestedContext!==!1},onChange:e=>oe({autoLoadNestedContext:e})})),J(o,V(Hl,{get value(){return e().hideThinkingBlock===!0},onChange:e=>oe({hideThinkingBlock:e})})),t})(),(()=>{var t=yl(),n=t.firstChild.nextSibling,r=n.firstChild.nextSibling,i=n.nextSibling.firstChild.nextSibling;return J(r,V(Hl,{get value(){return e().compactionEnabled!==!1},onChange:e=>oe({compactionEnabled:e})})),J(i,V(Hl,{get value(){return e().retryEnabled!==!1},onChange:e=>oe({retryEnabled:e})})),t})(),bl()]}),o),g.addEventListener(`change`,e=>io(e.currentTarget.checked)),J(w,V(H,{each:Ga,children:e=>(()=>{var t=kl(),n=t.firstChild,r=n.nextSibling;return n.addEventListener(`change`,t=>ao(e,t.currentTarget.checked)),q(n,`id`,`pref-tool-expand-${e}`),J(r,e),j(()=>n.checked=oo(e)),t})()})),J(D,(()=>{var e=G(()=>p()===`denied`);return()=>e()?`blocked by browser settings — re-enable notifications in site permissions`:G(()=>p()===`ios-install`)()?`iOS notifications need the installed PWA — tap Share → Add to Home Screen, then open dreb from the home screen icon`:p()===`unsupported`?`browser notifications are unavailable in this environment`:`show a notification when the tab needs input (Android/desktop need the app installed on mobile; works over HTTPS or localhost)`})()),O.addEventListener(`change`,e=>{e.currentTarget.checked&&ce()}),J(k,()=>p()===`granted`?`enabled`:`enable notifications`),J(ee,()=>M()?.supervised?`restarts the server process (a supervisor respawns it with the latest build) — kills all running sessions`:`exits the server process — only auto-restarts if run under a supervisor (systemd, pm2, …); otherwise the dashboard goes down. kills all running sessions`),te.$$click=()=>F(!0),J(te,()=>I()?`restarting…`:`restart`),J(o,V(U,{get when(){return re()},get children(){var e=il();return J(e,re),e}}),null),J(ne,()=>z()?.mode===`local`?`local`:`host`),J(L,V(U,{get when(){return G(()=>!!C()?.enabled)()&&C()?.code},get children(){var e=sl(),t=e.firstChild.nextSibling.firstChild;return J(t,()=>C().code),e}}),null),J(L,V(H,{get each(){return x()??[]},children:e=>(()=>{var t=Al(),n=t.firstChild,r=n.nextSibling,i=r.firstChild,a=r.nextSibling.firstChild;return J(n,()=>e.device??e.id),J(r,()=>e.identity,i),J(r,()=>it(e.createdAt),null),a.$$click=async()=>{await X.unpair(e.id),await S()},t})()}),null),J(L,V(U,{get when(){return(x()??[]).length===0},get children(){return cl()}}),null),J(ie,(()=>{var e=G(()=>!!M()?.version);return()=>e()?` v${M().version}`:G(()=>!!A())()?` v${A()}`:``})(),le),J(ie,V(U,{get when(){return M()?.startedAt},get children(){return[` · server build, up `,G(()=>it(M().startedAt))]}}),null),J(n,V(U,{get when(){return P()},get children(){return V(nt,{title:`restart dashboard service?`,onDismiss:()=>F(!1),get actions(){return[(()=>{var e=jl();return e.$$click=()=>F(!1),e})(),(()=>{var e=Ml();return e.$$click=()=>{F(!1),ae()},e})()]},get children(){var e=ll();return e.firstChild.nextSibling.nextSibling,J(e,()=>M()?.supervised?` A supervisor is detected — the server should respawn automatically with the latest build.`:` No supervisor was detected — the dashboard will NOT come back on its own; you'll need to relaunch it manually.`,null),e}})}}),null),J(n,V(U,{get when(){return s()},children:e=>{let t=()=>{let t=e();return t.kind==="default"?`select default model`:`add model for ${t.agentName}`},n=()=>{let t=e();return t.kind==="default"?h()?.defaultProvider&&h()?.defaultModel?[`${h().defaultProvider}/${h().defaultModel}`]:[]:R(t.agentName)};return V(Ul,{get title(){return t()},get models(){return v()??[]},get selected(){return n()},onClose:()=>c(void 0),onPick:t=>{let n=e();if(c(void 0),n.kind==="default"){oe({defaultProvider:t.provider,defaultModel:t.id});return}let r=Il(t),i=R(n.agentName);i.includes(r)||se(n.agentName,[...i,r])}})}}),null),j(e=>{var t=p()===`denied`||p()===`unsupported`||p()===`ios-install`,n=I();return t!==e.e&&(O.disabled=e.e=t),n!==e.t&&(te.disabled=e.t=n),e},{e:void 0,t:void 0}),j(()=>g.checked=ro()),j(()=>O.checked=p()===`granted`),n})()}Me([`input`,`click`]);var Gl=K(`<p class=pair-error>`),Kl=K(`<div class=status-line><span class=working>● working`),ql=K(`<div class=session-screen><header class=session-bar><div class=session-bar-inner><a class=back>← </a><span class=agent-type>subagent · </span><span class=title></span><span class=right></span></div></header><main class=chat><div class=chat-inner></div></main><footer class=dock><div class=dock-inner><div class=readonly-note>viewing live — subagents can't be steered yet; the parent session controls this agent.`),Jl=K(`<p class=muted>`);function Yl(e){let t=()=>e.store.sessions[e.sessionKey],n=N(()=>t()?.backgroundAgents[e.agentId]),r=()=>t()?.subagents[e.agentId],i=N(()=>e.store.fleet().runtimes.find(t=>t.key===e.sessionKey)),a=()=>i()?.state.sessionName??e.sessionKey,[o,s]=k(),c,l=!0,u=()=>!c||c.scrollTop+c.clientHeight>=c.scrollHeight-40,d=Ha({element:()=>c,shouldScroll:()=>l});return te(()=>{let t=new AbortController;e.store.hydrateSubagent(e.sessionKey,e.agentId,t.signal).catch(e=>{t.signal.aborted&&ss(e)||s(e instanceof Error?e.message:String(e))}),L(()=>t.abort())}),M(()=>{e.store.revisions[e.sessionKey],l&&d.request()}),L(()=>d.cancel()),(()=>{var t=ql(),i=t.firstChild,s=i.firstChild.firstChild;s.firstChild;var d=s.nextSibling;d.firstChild;var f=d.nextSibling,p=f.nextSibling,m=i.nextSibling,h=m.firstChild,g=m.nextSibling.firstChild,_=g.firstChild;J(s,a,null),J(d,()=>n()?.agentType??`unknown`,null),J(f,()=>n()?.taskSummary??e.agentId),J(p,V(U,{get when(){return n()?.status===`running`},get fallback(){return V($e,{get status(){return n()?.status===`failed`?`error`:`idle`},get label(){return n()?.status}})},get children(){return V($e,{status:`running`})}})),m.addEventListener(`scroll`,()=>{l=u()});var v=c;return typeof v==`function`?Ie(v,m):c=m,J(h,V(U,{get when(){return o()},get children(){var e=Gl();return J(e,o),e}}),null),J(h,V(U,{get when(){return G(()=>!!r())()&&r().entries.length>0},get fallback(){return(()=>{var e=Jl();return J(e,()=>n()?`waiting for output from this agent…`:`no data for this agent — it may not have started writing its session log yet.`),e})()},get children(){return V(os,{get entries(){return r().entries},get who(){return n()?.agentType??`agent`},userLabel:`task from parent`,get resetKey(){return`${e.sessionKey}:${e.agentId}`}})}}),null),J(g,V(U,{get when(){return r()?.streaming},get children(){return Kl()}}),_),j(()=>q(s,`href`,`#/session/${e.sessionKey}`)),t})()}function Xl(e){return{key:e,entries:[],streaming:!1,compacting:!1,statusEntries:[],tasks:[],backgroundAgents:{},uiRequests:[],toasts:[],widgets:{above:[],below:[]},needsAttention:!1,subagents:{}}}function Zl(e){return typeof e==`string`?e:Array.isArray(e)?e.filter(e=>typeof e?.text==`string`).map(e=>e.text).join(`
|
|
75
|
-
`):``}function
|
|
74
|
+
`)}async function Ot(){let t=r().trim();if(!t&&be().length===0&&ve().length===0)return;let n=Dt(t||`Please review the attached item(s). `);P(void 0);try{let t=ve(),r=t.length>0?await Promise.all(t.map(async({blob:e,mimeType:t})=>({data:await at(e),mimeType:t}))):void 0;Ue()?await X.prompt(e.sessionKey,n,a(),r):r?await X.prompt(e.sessionKey,n,void 0,r):await X.prompt(e.sessionKey,n),ps(e.sessionKey,n),Ce(void 0),i(``),Y(),xe([]),tt()}catch(e){P(e instanceof Error?e.message:String(e))}}async function kt(){c(!0);try{await X.abort(e.sessionKey),await ht(),await e.store.refreshFleet()}catch(e){P(e instanceof Error?e.message:String(e))}finally{c(!1)}}async function At(){if(!l()){u(!0),P(void 0);try{await X.stopRuntime(e.sessionKey),e.store.navigate({screen:`fleet`})}catch(e){P(e instanceof Error?e.message:String(e))}finally{u(!1)}}}async function jt(t){try{t===`compaction`?await X.abortCompaction(e.sessionKey):t===`retry`&&await X.abortRetry(e.sessionKey)}catch(e){P(e instanceof Error?e.message:String(e))}}function Mt(e){Me?.querySelectorAll(`details.tool`).forEach(t=>{t.open=e})}let Nt=()=>Object.values(t()?.backgroundAgents??{}).filter(e=>e.status===`running`),Pt=()=>Object.values(t()?.backgroundAgents??{}).filter(e=>e.status!==`running`),Ft=()=>t()?.tasks??[],It=()=>Ft().filter(e=>e.status===`completed`).length,Lt=()=>n()?.state.contextUsage??I()?.contextUsage,Rt=()=>typeof window.matchMedia==`function`&&window.matchMedia(`(max-width: 700px)`).matches,zt=()=>t()?.sessionName??n()?.state.sessionName,Bt=()=>zt()??t()?.title??e.sessionKey,Vt=()=>{let e=n()?.cwd;if(!e)return;let t=ae();return`${Lc(e)}${t?` (${t})`:``}`},Ht=()=>{let e=Vt(),t=zt();return e&&t?`${e} • ${t}`:e??t??`session`},Ut=()=>{let e=I()?.tokens;if(!e)return;let t=[];return e.input&&t.push(`↑${Ic(e.input)}`),e.output&&t.push(`↓${Ic(e.output)}`),e.cacheRead&&t.push(`R${Ic(e.cacheRead)}`),e.cacheWrite&&t.push(`W${Ic(e.cacheWrite)}`),t.length>0?t.join(` `):void 0},Wt=()=>{let e=I()?.cost??0,t=n()?.state.usingSubscription??!1;if(!e&&!t)return;let r=`$${e.toFixed(3)}${t?` (sub)`:``}`,i=se();return i!==void 0&&i>e&&(r+=`, today: $${i.toFixed(2)}`),r},Gt=()=>{let e=Lt();if(e)return`ctx ${e.percent===null?`?`:`${e.percent.toFixed(0)}%`}/${Ic(e.contextWindow)}`},Kt=()=>{let e=n()?.state.model;if(!e)return;let t=re()?.models.find(t=>t.provider===e.provider&&t.modelId===e.id);if(!(!t||t.count<3))return`${Math.round(t.median)} tok/s`},qt=()=>[Ut(),Wt(),Gt(),Kt()].filter(Boolean),Jt=()=>[...(ge().steeringMessages??ge().steering.map(e=>({text:e}))).map(e=>({kind:`steer`,text:e.images?.length?`${e.text} (${e.images.length} image(s))`:e.text})),...(ge().followUpMessages??ge().followUp.map(e=>({text:e}))).map(e=>({kind:`follow-up`,text:e.images?.length?`${e.text} (${e.images.length} image(s))`:e.text}))],Yt=()=>{let e=r();if(le()||!e.startsWith(`/`))return;let t=e.slice(1);if(!/\s/.test(t))return t.toLowerCase()},Xt=N(()=>{let e=Yt();return e===void 0?[]:ce().filter(t=>{let n=t.name.toLowerCase();return!e||n.startsWith(e)||n.includes(e)}).sort((t,n)=>!t.name.toLowerCase().startsWith(e)-+!n.name.toLowerCase().startsWith(e)||t.name.localeCompare(n.name)).slice(0,8)}),Zt=()=>Xt().length>0,Qt=e=>{i(`/${e.name} `),B(!0),queueMicrotask(()=>Pe?.focus())};return M(()=>{let e=Xt().length;ue()>=e&&de(Math.max(0,e-1))}),(()=>{var c=pc(),u=c.firstChild,k=u.firstChild,M=k.firstChild.nextSibling,N=M.nextSibling,F=u.nextSibling,te=F.firstChild,L=F.nextSibling,ne=L.firstChild,re=ne.firstChild;J(M,Bt),J(k,V(U,{get when(){return!h()},get children(){var e=Hs();return J(e,(()=>{var e=G(()=>!!n()?.cwd);return()=>e()?Lc(n().cwd):void 0})()),e}}),N),J(k,V(U,{get when(){return!h()},get children(){var t=Ws(),r=t.firstChild,i=r.firstChild.nextSibling.nextSibling,a=r.nextSibling;a.firstChild.nextSibling;var o=a.nextSibling;return r.$$click=()=>f(!0),J(i,()=>Pc(n()?.state.model)),a.$$click=async()=>{let t=n()?.state.thinkingLevel??`off`,r=jc[(jc.indexOf(t)+1)%jc.length];try{await X.setThinking(e.sessionKey,r),await e.store.refreshFleet()}catch(e){P(e instanceof Error?e.message:String(e))}},J(a,()=>n()?.state.thinkingLevel??`—`,null),J(t,V(U,{get when(){return Lt()},get children(){var e=Us();return e.firstChild.nextSibling,J(e,(()=>{var e=G(()=>Lt().percent===null);return()=>e()?`?`:`${Lt().percent.toFixed(0)}%`})(),null),e}}),o),o.$$click=()=>m(!p()),j(()=>q(r,`title`,Fc(n()?.state.model))),t}}),N),N.$$click=()=>g(!h()),J(N,()=>h()?`details ▾`:`details ▴`),J(u,V(U,{get when(){return!h()},get children(){return[(()=>{var e=Ks(),t=e.firstChild,n=t.nextSibling;return J(t,Ht),n.$$click=St,J(n,V(H,{get each(){return qt()},children:e=>(()=>{var t=mc();return J(t,e),t})()})),J(e,V(U,{get when(){return ke()},get children(){var e=Gs(),t=Re;return typeof t==`function`?Ie(t,e):Re=e,J(e,V(U,{get when(){return je()},get children(){var e=Es();return J(e,je),e}}),null),J(e,V(U,{get when(){return I()},get fallback(){return hc()},children:e=>(()=>{var t=gc(),n=t.firstChild.nextSibling,r=n.nextSibling.nextSibling,i=r.nextSibling.nextSibling,a=i.firstChild,o=i.nextSibling.nextSibling,s=o.firstChild,c=o.nextSibling.nextSibling,l=c.firstChild,u=c.nextSibling.nextSibling,d=u.nextSibling.nextSibling;return d.firstChild,J(n,()=>e().userMessages),J(r,()=>e().assistantMessages),J(i,()=>e().toolCalls,a),J(i,()=>e().toolResults,null),J(o,()=>Ic(e().tokens.input),s),J(o,()=>Ic(e().tokens.output),null),J(c,()=>Ic(e().tokens.cacheRead),l),J(c,()=>Ic(e().tokens.cacheWrite),null),J(u,()=>Ic(e().tokens.total)),J(d,()=>e().cost.toFixed(4),null),t})()}),null),e}}),null),e})(),V(U,{get when(){return p()},get children(){var t=Ys(),r=t.firstChild,i=r.nextSibling,a=i.nextSibling,o=a.nextSibling,s=o.nextSibling,c=s.nextSibling,u=c.nextSibling,d=u.nextSibling;return i.$$click=()=>S(!0),a.$$click=()=>Mt(!0),o.$$click=()=>Mt(!1),s.$$click=()=>w(!0),c.$$click=bt,u.$$click=et,J(t,V(U,{get when(){return Rt()},get children(){return[(()=>{var e=qs();return e.firstChild,e.$$click=()=>f(!0),J(e,()=>Pc(n()?.state.model),null),e})(),(()=>{var t=Js();return t.firstChild,t.$$click=async()=>{let t=n()?.state.thinkingLevel??`off`,r=jc[(jc.indexOf(t)+1)%jc.length];try{await X.setThinking(e.sessionKey,r),await e.store.refreshFleet()}catch(e){P(e instanceof Error?e.message:String(e))}},J(t,()=>n()?.state.thinkingLevel??`—`,null),t})()]}}),d),d.$$click=At,J(d,()=>l()?`stopping runtime…`:`stop runtime`),j(t=>{var n=X.exportHtmlUrl(e.sessionKey),i=l();return n!==t.e&&q(r,`href`,t.e=n),i!==t.t&&(d.disabled=t.t=i),t},{e:void 0,t:void 0}),t}})]}}),null),J(c,V(U,{get when(){return G(()=>!!(!h()&&n()?.state.modelFallbackMessage))()&&!D()},get children(){var e=Xs(),t=e.firstChild.firstChild;t.firstChild;var r=t.nextSibling;return J(t,()=>n().state.modelFallbackMessage,null),r.$$click=()=>O(!0),e}}),F),F.addEventListener(`scroll`,()=>{ze=Je()}),F.$$touchend=()=>{Be=!1,ze=Je()},F.$$touchstart=()=>{Be=!0},F.addEventListener(`wheel`,()=>{Be=!0,Xe()});var ie=Me;return typeof ie==`function`?Ie(ie,F):Me=F,J(te,V(U,{get when(){return t()},get fallback(){return _c()},get children(){return[V(H,{get each(){return t().widgets.above},children:e=>(()=>{var t=vc();return J(t,e),t})()}),V(os,{get entries(){return t().entries},get resetKey(){return e.sessionKey}}),V(H,{get each(){return t().widgets.below},children:e=>(()=>{var t=vc();return J(t,e),t})()})]}})),re.$$click=()=>v(!_()),J(re,()=>_()?`compose ▴`:`compose ▾`),J(ne,V(U,{get when(){return _()},get children(){var e=Zs();return J(e,(()=>{var e=G(()=>!!qe());return()=>e()?`agent working — open controls to stop or steer`:G(()=>Jt().length>0)()?`${Jt().length} queued message(s)`:`composer hidden for transcript reading`})()),e}}),null),J(L,V(U,{get when(){return!_()},get children(){var n=uc(),c=n.firstChild,l=c.firstChild,u=l.nextSibling,d=u.firstChild,f=d.nextSibling,p=f.nextSibling,m=p.nextSibling,h=m.nextSibling;J(n,V(U,{get when(){return Ft().length>0},get children(){var e=Qs(),t=e.firstChild,n=t.firstChild.nextSibling,r=n.nextSibling.nextSibling;r.nextSibling;var i=t.nextSibling;return J(t,It,n),J(t,()=>Ft().length,r),J(i,V(H,{get each(){return Ft()},children:e=>(()=>{var t=yc(),n=t.firstChild;return J(t,(()=>{var t=G(()=>e.status===`completed`);return()=>t()?`☑`:e.status===`in_progress`?`⧖`:`☐`})(),n),J(t,()=>e.title,null),j(n=>{var r=e.status===`completed`,i=e.status===`in_progress`;return r!==n.e&&t.classList.toggle(`done`,n.e=r),i!==n.t&&t.classList.toggle(`active`,n.t=i),n},{e:void 0,t:void 0}),t})()})),j(()=>e.open=!Rt()),e}}),c),J(n,V(U,{get when(){return Nt().length+Pt().length>0},get children(){var t=ec(),n=t.firstChild,r=n.nextSibling,i=r.firstChild.nextSibling,a=i.nextSibling.nextSibling;return a.nextSibling,n.$$click=()=>b(!y()),J(n,()=>y()?`subagents ▴`:`subagents ▾`),J(r,()=>Nt().length,i),J(r,()=>Pt().length,a),J(t,V(U,{get when(){return y()},get children(){return $s()}}),null),J(t,V(U,{get when(){return!y()},get children(){return V(H,{get each(){return[...Nt(),...Pt()].slice(0,4)},children:t=>(()=>{var n=bc(),r=n.firstChild,i=r.nextSibling,a=i.firstChild;return n.$$click=()=>e.store.navigate({screen:`subagent`,key:e.sessionKey,agentId:t.agentId}),J(r,(()=>{var e=G(()=>t.status===`running`);return()=>e()?`●`:t.status===`completed`?`✓`:`✕`})()),J(i,()=>t.agentType,a),J(i,()=>t.taskSummary,null),j(()=>Ne(r,t.status===`running`?`live`:`done`)),n})()})}}),null),j(e=>{var r=!!y(),i=y()?`show subagents`:`hide subagents`;return r!==e.e&&t.classList.toggle(`collapsed`,e.e=r),i!==e.t&&q(n,`title`,e.t=i),e},{e:void 0,t:void 0}),t}}),c),J(n,V(U,{get when(){return qe()||(t()?.statusEntries.length??0)>0||A()},get children(){var e=ic();return J(e,V(U,{get when(){return Ue()},get children(){var e=tc();return e.firstChild,J(e,(()=>{var e=G(()=>!!t()?.workingText);return()=>e()?` — ${t().workingText}`:``})(),null),J(e,(()=>{var e=G(()=>ee()>2);return()=>e()?` (${ee()}s)`:``})(),null),e}}),null),J(e,V(H,{get each(){return t()?.statusEntries??[]},children:e=>(()=>{var t=mc();return J(t,()=>e.text,null),J(t,V(U,{get when(){return e.key===`compaction`||e.key===`retry`},get children(){var t=xc();return t.$$click=()=>jt(e.key),t}}),null),j(()=>Ne(t,e.tone===`info`?`queued`:e.tone)),t})()}),null),J(e,V(U,{get when(){return A()},get children(){var e=nc();return J(e,A),e}}),null),J(e,V(U,{get when(){return qe()},get children(){var e=rc();return e.$$click=kt,J(e,()=>s()?`stopping…`:`■ stop`),j(()=>e.disabled=s()),e}}),null),e}}),c),J(c,V(U,{get when(){return Jt().length>0},get children(){var e=ac(),t=e.firstChild;return J(e,V(H,{get each(){return Jt()},children:e=>(()=>{var t=Sc(),n=t.firstChild;return J(n,()=>e.kind),J(t,()=>e.text,null),j(()=>q(t,`title`,e.text)),t})()}),t),t.$$click=ht,e}}),l),J(c,V(U,{get when(){return be().length>0||ve().length>0},get children(){var e=oc();return J(e,V(H,{get each(){return be()},children:(e,t)=>(()=>{var n=Cc(),r=n.firstChild;r.firstChild;var i=r.nextSibling,a=i.nextSibling;return J(r,()=>e.fileName,null),J(i,()=>Vc(e.size)),a.$$click=()=>xe(e=>e.filter((e,n)=>n!==t())),j(()=>q(n,`title`,e.path)),n})()}),null),J(e,V(H,{get each(){return ve()},children:(e,t)=>(()=>{var n=wc(),r=n.firstChild,i=r.nextSibling;return i.$$click=()=>st(t()),j(t=>{var i=`${e.fileName} (${Vc(e.size)})`,a=e.previewUrl,o=e.fileName;return i!==t.e&&q(n,`title`,t.e=i),a!==t.t&&q(r,`src`,t.t=a),o!==t.a&&q(r,`alt`,t.a=o),t},{e:void 0,t:void 0,a:void 0}),n})()}),null),e}}),l),J(c,V(U,{get when(){return Zt()},get children(){var e=sc();return J(e,V(H,{get each(){return Xt()},children:(e,t)=>(()=>{var n=Ec(),r=n.firstChild;r.firstChild;var i=r.nextSibling;return n.$$click=()=>Qt(e),n.addEventListener(`mouseenter`,()=>de(t())),J(r,()=>e.name,null),J(n,V(U,{get when(){return e.description},get children(){var t=Tc();return J(t,()=>e.description),t}}),i),J(i,()=>e.source),j(e=>{var r=`command-option-${t()}`,i=ue()===t(),a=ue()===t();return r!==e.e&&q(n,`id`,e.e=r),i!==e.t&&q(n,`aria-selected`,e.t=i),a!==e.a&&n.classList.toggle(`selected`,e.a=a),e},{e:void 0,t:void 0,a:void 0}),n})()})),e}}),l),l.$$keydown=t=>{if(Zt()){if(t.key===`ArrowDown`){t.preventDefault(),de((ue()+1)%Xt().length);return}if(t.key===`ArrowUp`){t.preventDefault(),de((ue()-1+Xt().length)%Xt().length);return}if(t.key===`Tab`||t.key===`Enter`&&!t.shiftKey&&!Rt()){t.preventDefault();let e=Xt()[ue()];e&&Qt(e);return}if(t.key===`Escape`){t.preventDefault(),B(!0);return}}if((t.key===`ArrowUp`||t.key===`ArrowDown`)&&(r()===``||Se()!==void 0)){let n=fs(e.sessionKey);if(n.length>0){if(t.preventDefault(),t.key===`ArrowUp`){let e=Se()===void 0?n.length-1:Math.max(0,Se()-1);Ce(e),i(n[e]??``)}else if(Se()!==void 0){let e=Se()+1;e>=n.length?(Ce(void 0),i(``)):(Ce(e),i(n[e]??``))}}return}t.key===`Enter`&&!t.shiftKey&&!Rt()&&(t.preventDefault(),Ot())},l.$$input=e=>{B(!1),de(0),Ce(void 0),i(e.currentTarget.value),Wc(e.currentTarget)},l.addEventListener(`paste`,e=>{let t=[...e.clipboardData?.items??[]].filter(e=>e.type.startsWith(`image/`)).map(e=>e.getAsFile()).filter(e=>!!e);t.length>0&&(e.preventDefault(),_t(t))});var g=Pe;typeof g==`function`?Ie(g,l):Pe=l,d.addEventListener(`change`,e=>{yt(e.currentTarget.files??[]),e.currentTarget.value=``});var _=Fe;typeof _==`function`?Ie(_,d):Fe=d,f.addEventListener(`change`,e=>{_t(e.currentTarget.files??[]),e.currentTarget.value=``});var v=Le;return typeof v==`function`?Ie(v,f):Le=f,p.$$click=()=>Fe?.click(),m.$$click=()=>Le?.click(),J(u,V(U,{get when(){return Ue()},get children(){var e=cc(),t=e.firstChild,n=t.nextSibling;return t.$$click=()=>o(`steer`),n.$$click=()=>o(`follow_up`),j(e=>{var r=a()===`steer`,i=a()===`follow_up`;return r!==e.e&&t.classList.toggle(`selected`,e.e=r),i!==e.t&&n.classList.toggle(`selected`,e.t=i),e},{e:void 0,t:void 0}),e}}),h),J(u,V(U,{get when(){return t()?.suggestedCommand},get children(){var e=lc(),n=e.firstChild.nextSibling;return e.$$click=()=>i(t().suggestedCommand),J(n,()=>t().suggestedCommand),e}}),h),h.$$click=Ot,j(e=>{var t=Ue()?`Message dreb — sends as steer while it works…`:`Message dreb…`,n=Zt()?`command-listbox`:void 0,r=Zt()?`command-option-${ue()}`:void 0;return t!==e.e&&q(l,`placeholder`,e.e=t),n!==e.t&&q(l,`aria-controls`,e.t=n),r!==e.a&&q(l,`aria-activedescendant`,e.a=r),e},{e:void 0,t:void 0,a:void 0}),j(()=>l.value=r()),n}}),null),J(c,V(U,{get when(){return t()?.uiRequests[0]},children:t=>V(Gc,{get request(){return t()},onRespond:async t=>{try{await X.extensionUiResponse(e.sessionKey,t)}catch(e){P(e instanceof Error?e.message:String(e))}}})}),null),J(c,V(U,{get when(){return d()},get children(){return V(qc,{get sessionKey(){return e.sessionKey},get state(){return n()?.state},onClose:()=>f(!1),onSelected:()=>e.store.refreshFleet()})}}),null),J(c,V(U,{get when(){return x()},get children(){return V(nt,{title:`compact context`,onDismiss:()=>S(!1),get actions(){return[(()=>{var e=ys();return e.$$click=()=>S(!1),e})(),(()=>{var t=Dc();return t.$$click=async()=>{S(!1);try{await X.compact(e.sessionKey)}catch(e){P(e instanceof Error?e.message:String(e))}},t})()]},get children(){return dc()}})}}),null),J(c,V(U,{get when(){return T()},get children(){return V(Kc,{get resources(){return fe()},get error(){return me()},onClose:()=>E(!1)})}}),null),J(c,V(U,{get when(){return we()},get children(){return V(nt,{title:`fork from message`,onDismiss:()=>Te(!1),get children(){return[V(U,{get when(){return Oe()},get children(){var e=Es();return J(e,Oe),e}}),V(U,{get when(){return Ee().length>0},get fallback(){return Oc()},get children(){var e=fc();return J(e,V(H,{get each(){return Ee()},children:e=>(()=>{var t=kc(),n=t.firstChild,r=n.nextSibling;return t.$$click=()=>xt(e.entryId),J(n,()=>e.entryId),J(r,()=>e.text),t})()})),e}})]}})}}),null),J(c,V(U,{get when(){return C()},get children(){return V(Yc,{get current(){return zt()??``},onClose:()=>w(!1),onRename:async t=>{try{await X.rename(e.sessionKey,t),await e.store.refreshFleet(),w(!1)}catch(e){P(e instanceof Error?e.message:String(e))}}})}}),null),j(e=>{var t=!!h(),n=h()?`show session details`:`hide session details`,r=!!_(),i=_()?`show composer and controls`:`hide composer and controls`;return t!==e.e&&u.classList.toggle(`collapsed`,e.e=t),n!==e.t&&q(N,`title`,e.t=n),r!==e.a&&L.classList.toggle(`collapsed`,e.a=r),i!==e.o&&q(re,`title`,e.o=i),e},{e:void 0,t:void 0,a:void 0,o:void 0}),c})()}function Yc(e){let[t,n]=k(e.current);return V(nt,{title:`rename session`,get onDismiss(){return e.onClose},get actions(){return[(()=>{var t=ys();return Pe(t,`click`,e.onClose,!0),t})(),(()=>{var n=Ac();return n.$$click=()=>e.onRename(t().trim()),j(()=>n.disabled=!t().trim()),n})()]},get children(){var e=_s(),r=e.firstChild;return r.$$input=e=>n(e.currentTarget.value),j(()=>r.value=t()),e}})}Me([`input`,`click`,`touchstart`,`touchend`,`keydown`]);var Xc=K(`<select><option value=on>on</option><option value=off>off`),Zc=K(`<div class=field style=margin-bottom:8px><input type=text placeholder="search models…">`),Qc=K(`<div class=model-list style=max-height:320px>`),$c=K(`<p class="muted small">No matching models.`),el=K(`<section class=model-provider-group><div class=model-provider-heading>`),tl=K(`<span class=model-name>`),nl=K(`<span class=model-reasoning>think`),rl=K(`<button type=button class=model-row><span class=model-current></span><span class=model-id></span><span class=model-provider-badge>`),il=K(`<div class=settings-error>`),al=K(`<div class=settings-warning>`),ol=K(`<p class="muted small"style=margin-bottom:16px>✓ saved`),sl=K(`<div class=setting-row><span class=setting-label><span class=name>pairing code</span><span class=hint>new devices enter this in the pairing screen; it rotates every 30s</span></span><span class=setting-control><code style=font-size:var(--fs-h2);letter-spacing:0.08em>`),cl=K(`<p class="muted small"style=padding-top:8px>No remote devices paired. Launch with <code>--remote --allow <identity></code> to enable Tailscale access.`),ll=K(`<p>This exits the dashboard server process and terminates <strong>all running sessions</strong>.`),ul=K(`<div class=screen-fill><main class="container settings-wrap"><h1>settings</h1><p class=settings-intro>Defaults for new sessions. Live sessions keep their current values — change those from the session view. Writes go to the global settings file on the host.</p><section class=settings-section><h2>dashboard</h2><div class=setting-row><span class=setting-label><span class=name>always expand thinking</span><span class=hint>this browser only — stored in localStorage, not the host settings file</span></span><span class=setting-control><label class=checkbox-control><input id=pref-expand-thinking type=checkbox><span>open by default</span></label></span></div><div class=setting-row><span class=setting-label><span class=name>auto-expand tool cards</span><span class=hint>this browser only — stored in localStorage, not the host settings file</span></span><span class=setting-control style=display:grid;gap:6px></span></div><div class=setting-row><span class=setting-label><span class=name>needs-attention notifications</span><span class=hint></span></span><span class=setting-control><label class=checkbox-control><input id=pref-notifications type=checkbox><span></span></label></span></div><div class=setting-row><span class=setting-label><span class=name>restart dashboard service</span><span class=hint></span></span><span class=setting-control><button type=button class="btn btn-small btn-danger"></button></span></div></section><section class=settings-section><h2>devices</h2><div class=device-row><span>this machine <span class=this-device></span></span><span class=meta>local · always allowed</span></div></section><footer>dreb<!> · dashboard`),dl=K(`<div>`),fl=K(`<p class=muted>`),pl=K(`<section class=settings-section><h2>model</h2><div class=setting-row><span class=setting-label><span class=name>default model</span><span class=hint>used by new sessions; validated against configured providers</span></span><span class=setting-control><button type=button class="btn btn-small model-picker-button"></button></span></div><div class=setting-row><span class=setting-label><span class=name>default thinking level</span><span class=hint></span></span><span class=setting-control><select></select></span></div><div class=setting-row><span class=setting-label><span class=name>transport</span><span class=hint>preferred model transport for new sessions</span></span><span class=setting-control><select>`),ml=K(`<div class="setting-row agent-context-row"><span class=setting-label><span class=name>agent definition context</span><span class=hint>choose a project to include its .dreb/agents definitions</span></span><span class=setting-control><select><option value>global/home only`),hl=K(`<section class=settings-section><h2>agent models</h2><p class="muted small"style=margin-bottom:8px>Per-agent fallback lists. First available model wins; empty lists revert to the default model. Agent definitions are loaded from an explicit project context so project-local agents do not depend on which runtime opened first.`),gl=K(`<section class=settings-section><h2>images</h2><div class=setting-row><span class=setting-label><span class=name>auto-resize images</span><span class=hint>resize image inputs before sending them to providers</span></span><span class=setting-control></span></div><div class=setting-row><span class=setting-label><span class=name>block images</span><span class=hint>prevent image inputs from being sent to providers</span></span><span class=setting-control>`),_l=K(`<section class=settings-section><h2>queueing</h2><div class=setting-row><span class=setting-label><span class=name>steering delivery</span><span class=hint>deliver queued steers all at once, or one per turn</span></span><span class=setting-control><select></select></span></div><div class=setting-row><span class=setting-label><span class=name>follow-up delivery</span><span class=hint>deliver queued follow-ups all at once, or one per turn</span></span><span class=setting-control><select>`),vl=K(`<section class=settings-section><h2>behavior</h2><div class=setting-row><span class=setting-label><span class=name>skill slash commands</span><span class=hint>register skills as slash commands in new sessions</span></span><span class=setting-control></span></div><div class=setting-row><span class=setting-label><span class=name>auto-load nested context</span><span class=hint>load nested AGENTS.md/CLAUDE.md when tools enter subdirectories</span></span><span class=setting-control></span></div><div class=setting-row><span class=setting-label><span class=name>hide thinking blocks</span><span class=hint>hide raw thinking blocks in rendered transcripts</span></span><span class=setting-control>`),yl=K(`<section class=settings-section><h2>reliability</h2><div class=setting-row><span class=setting-label><span class=name>auto-compaction</span><span class=hint>summarize old context when the window fills</span></span><span class=setting-control></span></div><div class=setting-row><span class=setting-label><span class=name>auto-retry</span><span class=hint>retry transient stream errors (rate limits, 5xx)</span></span><span class=setting-control>`),bl=K(`<p class="muted small settings-footnote">TUI-only settings (theme, cursor, editor) are managed in the terminal /settings menu.`),xl=K(`<option>`),Sl=K(`<p class="muted small">No agent definitions found.`),Cl=K(`<div class=agent-model-editor><button type=button class="btn btn-small">add model…`),wl=K(`<div class=agent-model-row><div class=agent-model-summary><span class=agent-model-name></span><span class=agent-model-description></span></div><div class=agent-model-fallbacks></div><button type=button class="btn btn-small agent-model-edit">`),Tl=K(`<span class="muted small">default`),El=K(`<span class=agent-model-chip>. `),Dl=K(`<p class="muted small">Using the default model.`),Ol=K(`<div class=agent-model-entry><span></span><div class=agent-model-entry-actions><button type=button class="btn btn-small">↑</button><button type=button class="btn btn-small">↓</button><button type=button class="btn btn-small">×`),kl=K(`<label class=checkbox-control><input type=checkbox><span>`),Al=K(`<div class=device-row><span></span><span class=meta> · paired </span><span class=actions><button type=button class="btn btn-small btn-danger">unpair`),jl=K(`<button type=button class="btn btn-small">cancel`),Ml=K(`<button type=button class="btn btn-small btn-danger">restart`),Nl=[`off`,`minimal`,`low`,`medium`,`high`,`xhigh`],Pl=[`all`,`one-at-a-time`],Fl=[`sse`,`websocket`,`auto`];function Il(e){return`${e.provider}/${e.id}`}function Ll(e){let t=Il(e);return e.name?`${t} — ${e.name}`:t}function Rl(e){return e.defaultProvider&&e.defaultModel?`${e.defaultProvider}/${e.defaultModel}`:`choose model…`}function zl(e,t){return`${e.provider}/${e.id} ${e.name??``}`.toLowerCase().includes(t)}function Bl(){if(typeof Notification>`u`){if(window.isSecureContext===!1)return`insecure`;let e=/iPad|iPhone|iPod/.test(navigator.userAgent),t=navigator.standalone===!0||window.matchMedia?.(`(display-mode: standalone)`)?.matches===!0;return e&&!t?`ios-install`:`unsupported`}return Notification.permission}function Vl(e){let t=new Map;for(let n of e){let e=t.get(n.provider)??[];e.push(n),t.set(n.provider,e)}return[...t.entries()].map(([e,t])=>({provider:e,models:t}))}function Hl(e,t,n){let r=t+n;if(r<0||r>=e.length)return e;let i=[...e];return[i[t],i[r]]=[i[r],i[t]],i}function Ul(e){return(()=>{var t=Xc();return t.addEventListener(`change`,t=>e.onChange(t.currentTarget.value===`on`)),j(()=>t.value=e.value?`on`:`off`),t})()}function Wl(e){let[t,n]=k(``),r=()=>new Set(e.selected??[]),i=N(()=>{let n=t().toLowerCase();return Vl(e.models.filter(e=>!n||zl(e,n)).slice(0,100))}),a=e=>r().has(Il(e));return V(nt,{get title(){return e.title},get onDismiss(){return e.onClose},class:`model-picker-modal`,get children(){return[(()=>{var e=Zc(),r=e.firstChild;return r.$$input=e=>n(e.currentTarget.value),j(()=>r.value=t()),e})(),(()=>{var t=Qc();return J(t,V(U,{get when(){return i().length>0},get fallback(){return $c()},get children(){return V(H,{get each(){return i()},children:t=>(()=>{var n=el(),r=n.firstChild;return J(r,()=>t.provider),J(n,V(H,{get each(){return t.models},children:t=>(()=>{var n=rl(),r=n.firstChild,i=r.nextSibling,o=i.nextSibling;return n.$$click=()=>e.onPick(t),J(r,()=>a(t)?`✓`:``),J(i,()=>t.id),J(n,V(U,{get when(){return t.name},get children(){var e=tl();return J(e,()=>t.name),e}}),o),J(o,()=>t.provider),J(n,V(U,{get when(){return t.reasoning},get children(){return nl()}}),null),j(e=>{var r=!!a(t),i=Ll(t);return r!==e.e&&n.classList.toggle(`current`,e.e=r),i!==e.t&&q(n,`title`,e.t=i),e},{e:void 0,t:void 0}),n})()}),null),n})()})}})),t})()]}})}function Gl(e){let[t,n]=k(),[r,i]=k([]),[a,o]=k(!1),[s,c]=k(),[l,u]=k(),[d,f]=k(),[p,m]=k(Bl()),[h,{mutate:g,refetch:_}]=ee(async()=>{n(void 0);try{return await X.settings()}catch(e){n(e instanceof Error?e.message:String(e));return}}),[v]=ee(h,async()=>{try{let{models:e}=await X.settingsModels();return e}catch(e){return n(e instanceof Error?e.message:String(e)),[]}}),y=N(()=>{let t=new Set;for(let n of e.store.fleet().runtimes)t.add(n.cwd);for(let n of e.store.fleet().diskSessions)t.add(n.cwd);return[...t].sort((e,t)=>e.localeCompare(t))}),[b]=ee(()=>({settings:h(),cwd:d()}),async({cwd:e})=>{if(!h())return[];try{let{agentTypes:t}=await X.agentTypes(e);return t}catch(e){return n(e instanceof Error?e.message:String(e)),[]}}),[x,{refetch:S}]=ee(async()=>{let{devices:e}=await X.devices();return e}),[C,w]=k(),T;function E(){T&&clearTimeout(T),T=void 0}function D(e){E();let t=Math.max(250,e??3e4)+100;T=setTimeout(()=>void O(),t)}async function O(){try{let e=await X.pairingCode();if(!e.enabled||!e.code){w(void 0),E();return}w(e),D(e.expiresInMs)}catch(e){console.warn(`pairing code unavailable`,e),w(void 0),E()}}let[A]=ee(async()=>{try{let{version:e}=await X.version();return e}catch{return}}),[M]=ee(async()=>{try{return await X.serverInfo()}catch{return}}),[P,F]=k(!1),[I,ne]=k(!1),[re,ie]=k();async function ae(){ie(void 0),ne(!0);try{await X.restartServer()}catch(e){ie(e instanceof Error?e.message:String(e)),ne(!1)}}async function oe(e){n(void 0),i([]),o(!1);try{let t=await X.saveSettings(e);g(t),i(t.warnings??[]),o(!0),setTimeout(()=>o(!1),2e3)}catch(e){n(e instanceof Error?e.message:String(e)),await _()}}async function se(e,t){await oe({agentModels:{[e]:t}})}function R(e){return h()?.agentModels?.[e]??[]}async function ce(){typeof Notification>`u`||m(await Notification.requestPermission())}te(()=>void O()),L(E);let z=()=>e.store.auth();return(()=>{var n=ul(),i=n.firstChild,o=i.firstChild.nextSibling.nextSibling,m=o.firstChild.nextSibling,g=m.firstChild.nextSibling.firstChild.firstChild,_=m.nextSibling,w=_.firstChild.nextSibling,T=_.nextSibling,E=T.firstChild,D=E.firstChild.nextSibling,O=E.nextSibling.firstChild.firstChild,k=O.nextSibling,N=T.nextSibling.firstChild,ee=N.firstChild.nextSibling,te=N.nextSibling.firstChild,L=o.nextSibling,ne=L.firstChild.nextSibling.firstChild.firstChild.nextSibling,ie=L.nextSibling,le=ie.firstChild.nextSibling;return le.nextSibling,J(n,V(tt,{get store(){return e.store},active:`settings`}),i),J(i,V(U,{get when(){return t()},get children(){var e=il();return J(e,t),e}}),o),J(i,V(U,{get when(){return r().length>0},get children(){var e=al();return J(e,V(H,{get each(){return r()},children:e=>(()=>{var t=dl();return J(t,e),t})()})),e}}),o),J(i,V(U,{get when(){return a()},get children(){return ol()}}),o),J(i,V(U,{get when(){return h()},get fallback(){return(()=>{var e=fl();return J(e,()=>t()?`Settings could not be loaded — see the error above.`:`Loading settings…`),e})()},children:e=>[(()=>{var t=pl(),n=t.firstChild.nextSibling,r=n.firstChild.nextSibling.firstChild,i=n.nextSibling,a=i.firstChild,o=a.firstChild.nextSibling,s=a.nextSibling.firstChild,l=i.nextSibling.firstChild.nextSibling.firstChild;return r.$$click=()=>c({kind:`default`}),J(r,()=>Rl(e())),J(o,()=>Nl.join(` · `)),s.addEventListener(`change`,e=>oe({defaultThinkingLevel:e.currentTarget.value})),J(s,V(H,{each:Nl,children:e=>(()=>{var t=xl();return t.value=e,J(t,e),t})()})),l.addEventListener(`change`,e=>oe({transport:e.currentTarget.value})),J(l,V(H,{get each(){return[...Fl]},children:e=>(()=>{var t=xl();return t.value=e,J(t,e),t})()})),j(()=>s.value=e().defaultThinkingLevel??`off`),j(()=>l.value=e().transport??`sse`),t})(),(()=>{var t=hl();return t.firstChild.nextSibling,J(t,V(U,{get when(){return y().length>0},get children(){var e=ml(),t=e.firstChild.nextSibling.firstChild;return t.firstChild,t.addEventListener(`change`,e=>f(e.currentTarget.value||void 0)),J(t,V(H,{get each(){return y()},children:e=>(()=>{var t=xl();return t.value=e,J(t,e),t})()}),null),j(()=>t.value=d()??``),e}}),null),J(t,V(U,{get when(){return(b()??[]).length>0},get fallback(){return Sl()},get children(){return V(H,{get each(){return b()??[]},children:t=>{let n=()=>e().agentModels?.[t.name]??[];return(()=>{var e=wl(),r=e.firstChild,i=r.firstChild,a=i.nextSibling,o=r.nextSibling,s=o.nextSibling;return J(i,()=>t.name),J(a,()=>t.description),J(o,V(U,{get when(){return n().length>0},get fallback(){return Tl()},get children(){return V(H,{get each(){return n()},children:(e,t)=>(()=>{var n=El(),r=n.firstChild;return J(n,()=>t()+1,r),J(n,e,null),n})()})}})),s.$$click=()=>u(l()===t.name?void 0:t.name),J(s,()=>l()===t.name?`done`:`edit`),J(e,V(U,{get when(){return l()===t.name},get children(){var e=Cl(),r=e.firstChild;return J(e,V(U,{get when(){return n().length>0},get fallback(){return Dl()},get children(){return V(H,{get each(){return n()},children:(e,r)=>(()=>{var i=Ol(),a=i.firstChild,o=a.nextSibling.firstChild,s=o.nextSibling,c=s.nextSibling;return J(a,e),o.$$click=()=>void se(t.name,Hl(n(),r(),-1)),s.$$click=()=>void se(t.name,Hl(n(),r(),1)),c.$$click=()=>void se(t.name,n().filter((e,t)=>t!==r())),j(e=>{var t=r()===0,i=r()===n().length-1;return t!==e.e&&(o.disabled=e.e=t),i!==e.t&&(s.disabled=e.t=i),e},{e:void 0,t:void 0}),i})()})}}),r),r.$$click=()=>c({kind:`agent`,agentName:t.name}),e}}),null),e})()}})}}),null),t})(),(()=>{var t=gl(),n=t.firstChild.nextSibling,r=n.firstChild.nextSibling,i=n.nextSibling.firstChild.nextSibling;return J(r,V(Ul,{get value(){return e().imageAutoResize!==!1},onChange:e=>oe({imageAutoResize:e})})),J(i,V(Ul,{get value(){return e().blockImages===!0},onChange:e=>oe({blockImages:e})})),t})(),(()=>{var t=_l(),n=t.firstChild.nextSibling,r=n.firstChild.nextSibling.firstChild,i=n.nextSibling.firstChild.nextSibling.firstChild;return r.addEventListener(`change`,e=>oe({steeringMode:e.currentTarget.value})),J(r,V(H,{get each(){return[...Pl]},children:e=>(()=>{var t=xl();return t.value=e,J(t,e),t})()})),i.addEventListener(`change`,e=>oe({followUpMode:e.currentTarget.value})),J(i,V(H,{get each(){return[...Pl]},children:e=>(()=>{var t=xl();return t.value=e,J(t,e),t})()})),j(()=>r.value=e().steeringMode??`all`),j(()=>i.value=e().followUpMode??`all`),t})(),(()=>{var t=vl(),n=t.firstChild.nextSibling,r=n.firstChild.nextSibling,i=n.nextSibling,a=i.firstChild.nextSibling,o=i.nextSibling.firstChild.nextSibling;return J(r,V(Ul,{get value(){return e().enableSkillCommands!==!1},onChange:e=>oe({enableSkillCommands:e})})),J(a,V(Ul,{get value(){return e().autoLoadNestedContext!==!1},onChange:e=>oe({autoLoadNestedContext:e})})),J(o,V(Ul,{get value(){return e().hideThinkingBlock===!0},onChange:e=>oe({hideThinkingBlock:e})})),t})(),(()=>{var t=yl(),n=t.firstChild.nextSibling,r=n.firstChild.nextSibling,i=n.nextSibling.firstChild.nextSibling;return J(r,V(Ul,{get value(){return e().compactionEnabled!==!1},onChange:e=>oe({compactionEnabled:e})})),J(i,V(Ul,{get value(){return e().retryEnabled!==!1},onChange:e=>oe({retryEnabled:e})})),t})(),bl()]}),o),g.addEventListener(`change`,e=>io(e.currentTarget.checked)),J(w,V(H,{each:Ga,children:e=>(()=>{var t=kl(),n=t.firstChild,r=n.nextSibling;return n.addEventListener(`change`,t=>ao(e,t.currentTarget.checked)),q(n,`id`,`pref-tool-expand-${e}`),J(r,e),j(()=>n.checked=oo(e)),t})()})),J(D,(()=>{var e=G(()=>p()===`denied`);return()=>e()?`blocked by browser settings — re-enable notifications in site permissions`:G(()=>p()===`insecure`)()?`this page is not a secure context — notifications need HTTPS. Run the server with --https (tailscale cert <host>.<tailnet>.ts.net) and open it via the https:// hostname`:G(()=>p()===`ios-install`)()?`iOS notifications need the installed PWA — tap Share → Add to Home Screen, then open dreb from the home screen icon`:p()===`unsupported`?`browser notifications are unavailable in this environment`:`show a notification when the tab needs input (Android/desktop need the app installed on mobile; works over HTTPS or localhost)`})()),O.addEventListener(`change`,e=>{e.currentTarget.checked&&ce()}),J(k,()=>p()===`granted`?`enabled`:`enable notifications`),J(ee,()=>M()?.supervised?`restarts the server process (a supervisor respawns it with the latest build) — kills all running sessions`:`exits the server process — only auto-restarts if run under a supervisor (systemd, pm2, …); otherwise the dashboard goes down. kills all running sessions`),te.$$click=()=>F(!0),J(te,()=>I()?`restarting…`:`restart`),J(o,V(U,{get when(){return re()},get children(){var e=il();return J(e,re),e}}),null),J(ne,()=>z()?.mode===`local`?`local`:`host`),J(L,V(U,{get when(){return G(()=>!!C()?.enabled)()&&C()?.code},get children(){var e=sl(),t=e.firstChild.nextSibling.firstChild;return J(t,()=>C().code),e}}),null),J(L,V(H,{get each(){return x()??[]},children:e=>(()=>{var t=Al(),n=t.firstChild,r=n.nextSibling,i=r.firstChild,a=r.nextSibling.firstChild;return J(n,()=>e.device??e.id),J(r,()=>e.identity,i),J(r,()=>it(e.createdAt),null),a.$$click=async()=>{await X.unpair(e.id),await S()},t})()}),null),J(L,V(U,{get when(){return(x()??[]).length===0},get children(){return cl()}}),null),J(ie,(()=>{var e=G(()=>!!M()?.version);return()=>e()?` v${M().version}`:G(()=>!!A())()?` v${A()}`:``})(),le),J(ie,V(U,{get when(){return M()?.startedAt},get children(){return[` · server build, up `,G(()=>it(M().startedAt))]}}),null),J(n,V(U,{get when(){return P()},get children(){return V(nt,{title:`restart dashboard service?`,onDismiss:()=>F(!1),get actions(){return[(()=>{var e=jl();return e.$$click=()=>F(!1),e})(),(()=>{var e=Ml();return e.$$click=()=>{F(!1),ae()},e})()]},get children(){var e=ll();return e.firstChild.nextSibling.nextSibling,J(e,()=>M()?.supervised?` A supervisor is detected — the server should respawn automatically with the latest build.`:` No supervisor was detected — the dashboard will NOT come back on its own; you'll need to relaunch it manually.`,null),e}})}}),null),J(n,V(U,{get when(){return s()},children:e=>{let t=()=>{let t=e();return t.kind==="default"?`select default model`:`add model for ${t.agentName}`},n=()=>{let t=e();return t.kind==="default"?h()?.defaultProvider&&h()?.defaultModel?[`${h().defaultProvider}/${h().defaultModel}`]:[]:R(t.agentName)};return V(Wl,{get title(){return t()},get models(){return v()??[]},get selected(){return n()},onClose:()=>c(void 0),onPick:t=>{let n=e();if(c(void 0),n.kind==="default"){oe({defaultProvider:t.provider,defaultModel:t.id});return}let r=Il(t),i=R(n.agentName);i.includes(r)||se(n.agentName,[...i,r])}})}}),null),j(e=>{var t=p()===`denied`||p()===`unsupported`||p()===`ios-install`||p()===`insecure`,n=I();return t!==e.e&&(O.disabled=e.e=t),n!==e.t&&(te.disabled=e.t=n),e},{e:void 0,t:void 0}),j(()=>g.checked=ro()),j(()=>O.checked=p()===`granted`),n})()}Me([`input`,`click`]);var Kl=K(`<p class=pair-error>`),ql=K(`<div class=status-line><span class=working>● working`),Jl=K(`<div class=session-screen><header class=session-bar><div class=session-bar-inner><a class=back>← </a><span class=agent-type>subagent · </span><span class=title></span><span class=right></span></div></header><main class=chat><div class=chat-inner></div></main><footer class=dock><div class=dock-inner><div class=readonly-note>viewing live — subagents can't be steered yet; the parent session controls this agent.`),Yl=K(`<p class=muted>`);function Xl(e){let t=()=>e.store.sessions[e.sessionKey],n=N(()=>t()?.backgroundAgents[e.agentId]),r=()=>t()?.subagents[e.agentId],i=N(()=>e.store.fleet().runtimes.find(t=>t.key===e.sessionKey)),a=()=>i()?.state.sessionName??e.sessionKey,[o,s]=k(),c,l=!0,u=()=>!c||c.scrollTop+c.clientHeight>=c.scrollHeight-40,d=Ha({element:()=>c,shouldScroll:()=>l});return te(()=>{let t=new AbortController;e.store.hydrateSubagent(e.sessionKey,e.agentId,t.signal).catch(e=>{t.signal.aborted&&ss(e)||s(e instanceof Error?e.message:String(e))}),L(()=>t.abort())}),M(()=>{e.store.revisions[e.sessionKey],l&&d.request()}),L(()=>d.cancel()),(()=>{var t=Jl(),i=t.firstChild,s=i.firstChild.firstChild;s.firstChild;var d=s.nextSibling;d.firstChild;var f=d.nextSibling,p=f.nextSibling,m=i.nextSibling,h=m.firstChild,g=m.nextSibling.firstChild,_=g.firstChild;J(s,a,null),J(d,()=>n()?.agentType??`unknown`,null),J(f,()=>n()?.taskSummary??e.agentId),J(p,V(U,{get when(){return n()?.status===`running`},get fallback(){return V($e,{get status(){return n()?.status===`failed`?`error`:`idle`},get label(){return n()?.status}})},get children(){return V($e,{status:`running`})}})),m.addEventListener(`scroll`,()=>{l=u()});var v=c;return typeof v==`function`?Ie(v,m):c=m,J(h,V(U,{get when(){return o()},get children(){var e=Kl();return J(e,o),e}}),null),J(h,V(U,{get when(){return G(()=>!!r())()&&r().entries.length>0},get fallback(){return(()=>{var e=Yl();return J(e,()=>n()?`waiting for output from this agent…`:`no data for this agent — it may not have started writing its session log yet.`),e})()},get children(){return V(os,{get entries(){return r().entries},get who(){return n()?.agentType??`agent`},userLabel:`task from parent`,get resetKey(){return`${e.sessionKey}:${e.agentId}`}})}}),null),J(g,V(U,{get when(){return r()?.streaming},get children(){return ql()}}),_),j(()=>q(s,`href`,`#/session/${e.sessionKey}`)),t})()}function Zl(e){return{key:e,entries:[],streaming:!1,compacting:!1,statusEntries:[],tasks:[],backgroundAgents:{},uiRequests:[],toasts:[],widgets:{above:[],below:[]},needsAttention:!1,subagents:{}}}function Ql(e){return typeof e==`string`?e:Array.isArray(e)?e.filter(e=>typeof e?.text==`string`).map(e=>e.text).join(`
|
|
75
|
+
`):``}function $l(e,t){let n=e.match(/<background-agent-complete>\n?([\s\S]*?)\n?<\/background-agent-complete>/)?.[1]?.trim();if(!n)return;let r=n.split(`
|
|
76
76
|
`),i=r.findIndex(e=>e.trim().length>0),a,o=n;return i>=0&&/^Background agent .+\.$/.test(r[i]?.trim()??``)&&(a=r[i].trim(),o=r.slice(i+1).join(`
|
|
77
|
-
`).trim()||a),{kind:`agent-result`,text:o,header:a,raw:e,timestamp:t}}function $l(e){let t=Zl(e.content);return Ql(t,e.timestamp)??{kind:`user`,text:t,timestamp:e.timestamp}}function eu(e){let t=[],n=new Map;for(let r of e)if(r.role===`user`)t.push($l(r));else if(r.role===`assistant`){let e=[],i=()=>{e.length!==0&&(t.push({kind:`assistant`,blocks:e,model:r.model,streaming:!1,timestamp:r.timestamp}),e=[])};if(Array.isArray(r.content)){for(let a of r.content)if(a.type===`text`&&typeof a.text==`string`)e.push({kind:`text`,text:a.text});else if(a.type===`thinking`&&typeof a.thinking==`string`)e.push({kind:`thinking`,text:a.thinking});else if(a.type===`toolCall`){i();let e=a,o={kind:`tool`,toolCallId:String(e.id??``),toolName:String(e.name??`tool`),args:e.arguments,status:`running`,resultText:``,startedAt:r.timestamp??Date.now()};n.set(o.toolCallId,o),t.push(o)}}i()}else if(r.role===`toolResult`){let e=n.get(String(r.toolCallId));e&&(e.status=r.isError?`error`:`done`,e.resultText=Zl(r.content),e.details=r.details,e.endedAt=r.timestamp)}else if(r.role===`bashExecution`){let e=r;t.push({kind:`tool`,toolCallId:`bash-exec-${t.length}`,toolName:`bash (user)`,args:{command:e.command},status:`done`,resultText:e.output??``,startedAt:r.timestamp??Date.now()})}else if(r.role===`custom`){let e=r;t.push({kind:`custom`,tag:e.customType??`extension`,text:e.displayText??Zl(r.content)})}return t}var tu=0,nu=20;function ru(e){let t=e.toasts.length-nu;t>0&&e.toasts.splice(0,t)}function iu(e){let t=Date.parse(e.startedAt);return Number.isFinite(t)?t:0}function au(e){let t=Object.values(e.backgroundAgents).filter(e=>e.status!==`running`).sort((e,t)=>iu(e)-iu(t)||e.agentId.localeCompare(t.agentId)),n=t.length-20;if(!(n<=0))for(let r of t.slice(0,n))delete e.backgroundAgents[r.agentId],delete e.subagents[r.agentId]}function ou(e){e.needsAttention=e.uiRequests.length>0||e.statusEntries.some(e=>e.tone===`error`)||!!e.suggestedCommand}function su(e){for(let t=e.entries.length-1;t>=0;t--){let n=e.entries[t];if(n.kind===`assistant`)return n}}function cu(e,t){for(let n=e.entries.length-1;n>=0;n--){let r=e.entries[n];if(r.kind===`tool`&&r.toolCallId===t)return r}}function lu(e,t){switch(t.type){case`agent_start`:e.streaming=!0;break;case`agent_end`:{e.streaming=!1;let t=su(e);t&&(t.streaming=!1);break}case`message_start`:{let n=t.message;n?.role===`user`?e.entries.push($l(n)):n?.role===`assistant`&&e.entries.push({kind:`assistant`,blocks:[],model:n.model,streaming:!0,timestamp:n.timestamp});break}case`message_update`:{let n=t.assistantMessageEvent;if(!n)break;let r=su(e);if((!r||!r.streaming)&&(r={kind:`assistant`,blocks:[],streaming:!0},e.entries.push(r)),n.type===`text_start`)r.blocks.push({kind:`text`,text:``});else if(n.type===`thinking_start`)r.blocks.push({kind:`thinking`,text:``});else if(n.type===`text_delta`||n.type===`thinking_delta`){let e=r.blocks[r.blocks.length-1];e&&(e.text+=n.delta??``)}else if(n.type===`text_end`||n.type===`thinking_end`){let e=r.blocks[r.blocks.length-1];e&&typeof n.content==`string`&&(e.text=n.content)}break}case`message_end`:{let n=t.message;if(n?.role===`assistant`){let t=su(e);if(t?.streaming){let e=eu([n]).find(e=>e.kind===`assistant`);e&&(t.blocks=e.blocks,t.model=e.model),t.streaming=!1}else t||e.entries.push(...eu([n]))}else if(n?.role===`user`){let t=e.entries[e.entries.length-1],r=$l(n);r.kind===`agent-result`?t?.kind===`agent-result`&&t.raw===r.raw||e.entries.push(r):t?.kind===`user`&&t.text===r.text||e.entries.push(r)}break}case`tool_execution_start`:e.entries.push({kind:`tool`,toolCallId:String(t.toolCallId),toolName:String(t.toolName),args:t.args,status:`running`,resultText:``,startedAt:Date.now()});break;case`tool_execution_update`:{let n=cu(e,String(t.toolCallId));if(n){let e=t.partialResult;n.resultText=typeof e==`string`?e:e?.content?Zl(e.content):n.resultText}break}case`tool_execution_end`:{let n=cu(e,String(t.toolCallId));if(n){n.status=t.isError?`error`:`done`;let e=t.result;n.resultText=typeof e==`string`?e:e?.content?Zl(e.content):n.resultText,n.details=t.result?.details,n.endedAt=Date.now()}break}default:break}}function uu(e,t){switch(lu(e,t),t.type){case`agent_start`:e.workingText=`working`,e.workingSince=Date.now(),e.lastError=void 0,e.suggestedCommand=void 0,e.uiRequests=[];break;case`agent_end`:e.workingText=void 0,e.workingSince=void 0,e.statusEntries=e.statusEntries.filter(e=>e.key!==`retry`&&e.key!==`paused`);break;case`tool_execution_start`:e.workingText=`${t.toolName}`;break;case`auto_compaction_start`:e.compacting=!0,e.statusEntries.push({key:`compaction`,text:`compacting context…`,tone:`info`});break;case`auto_compaction_end`:{e.compacting=!1,e.statusEntries=e.statusEntries.filter(e=>e.key!==`compaction`);let n=t.result;n&&!t.aborted&&e.entries.push({kind:`summary`,label:`compaction`,text:n.summary??`context compacted`,tokensBefore:n.tokensBefore}),t.errorMessage&&e.statusEntries.push({key:`compaction-error`,text:String(t.errorMessage),tone:`error`});break}case`auto_retry_start`:e.statusEntries=e.statusEntries.filter(e=>e.key!==`retry`),e.statusEntries.push({key:`retry`,text:`retrying (${t.attempt}/${t.maxAttempts}) — ${t.errorMessage}`,tone:`warning`});break;case`auto_retry_end`:e.statusEntries=e.statusEntries.filter(e=>e.key!==`retry`),!t.success&&t.finalError&&(e.statusEntries.push({key:`error`,text:String(t.finalError),tone:`error`}),e.lastError=String(t.finalError));break;case`stream_retry`:{e.statusEntries=e.statusEntries.filter(e=>e.key!==`retry`),e.statusEntries.push({key:`retry`,text:`stream dropped, retrying (${t.attempt}/${t.maxAttempts})`,tone:`warning`});let n=su(e);n?.streaming&&e.entries.splice(e.entries.indexOf(n),1);break}case`length_retry`:{e.statusEntries=e.statusEntries.filter(e=>e.key!==`retry`),e.statusEntries.push({key:`retry`,text:`response truncated, retrying with larger budget (${t.attempt}/${t.maxAttempts})`,tone:`warning`});let n=su(e);n?.streaming&&e.entries.splice(e.entries.indexOf(n),1);break}case`tasks_update`:e.tasks=t.tasks??[];break;case`suggest_next`:e.suggestedCommand=String(t.command);break;case`session_name_changed`:e.sessionName=String(t.name??``);break;case`background_agent_start`:e.backgroundAgents[String(t.agentId)]={agentId:String(t.agentId),agentType:String(t.agentType),taskSummary:String(t.taskSummary),startedAt:new Date().toISOString(),status:`running`,sessionDir:t.sessionDir};break;case`background_agent_end`:{let n=e.backgroundAgents[String(t.agentId)];n&&(n.status=t.success?`completed`:`failed`,n.sessionFile=t.sessionFile??n.sessionFile);let r=e.subagents[String(t.agentId)];r&&(r.streaming=!1),au(e);break}case`background_agent_event`:{let n=String(t.agentId),r=e.subagents[n];r||(r={agentId:n,entries:[],streaming:!0},e.subagents[n]=r);let i=t.event;if(i?.type===`session`)break;i?.type===`agent_start`&&i.model&&(r.model=i.model.id),lu(r,i);break}case`parent_paused_for_background_agents`:e.statusEntries=e.statusEntries.filter(e=>e.key!==`paused`),e.statusEntries.push({key:`paused`,text:`paused — waiting on ${t.runningAgentCount} background agent${t.runningAgentCount===1?``:`s`}`,tone:`warning`});break;case`extension_ui_request`:{let n=t.method;if(n===`select`||n===`confirm`||n===`input`||n===`editor`)e.uiRequests.push({id:String(t.id),method:n,title:String(t.title??``),message:t.message,options:t.options,prefill:t.prefill,placeholder:t.placeholder});else if(n===`notify`){tu+=1;let n=t.notifyType??`info`;e.toasts.push({id:tu,text:String(t.message??``),tone:n}),ru(e)}else if(n===`setStatus`){let n=`ext:${t.statusKey??`default`}`;e.statusEntries=e.statusEntries.filter(e=>e.key!==n),t.statusText&&e.statusEntries.push({key:n,text:String(t.statusText),tone:`info`})}else if(n===`setWidget`){let n=t.widgetPlacement===`belowEditor`?`below`:`above`;e.widgets[n]=Array.isArray(t.widgetLines)?t.widgetLines:[]}else n===`setTitle`?e.title=String(t.title??``):n===`set_editor_text`&&(e.composerPrefill=String(t.text??``));break}case`extension_error`:tu+=1,e.toasts.push({id:tu,text:`extension error: ${t.error}`,tone:`error`}),ru(e);break;default:break}ou(e)}function du(e,t){e.toasts=e.toasts.filter(e=>e.id!==t)}var fu=Symbol(`store-raw`),pu=Symbol(`store-node`),mu=Symbol(`store-has`),hu=Symbol(`store-self`);function gu(e){let t=e[p];if(!t&&(Object.defineProperty(e,p,{value:t=new Proxy(e,wu)}),!Array.isArray(e))){let n=Object.keys(e),r=Object.getOwnPropertyDescriptors(e),i=Object.getPrototypeOf(e),a=i!==null&&typeof e==`object`&&!!e&&!Array.isArray(e)&&i!==Object.prototype;if(a){let e=Object.getOwnPropertyDescriptors(i);n.push(...Object.keys(e)),Object.assign(r,e)}for(let i=0,o=n.length;i<o;i++){let o=n[i];a&&o===`constructor`||r[o].get&&Object.defineProperty(e,o,{configurable:!0,enumerable:r[o].enumerable,get:r[o].get.bind(t)})}}return t}function _u(e){let t;return typeof e==`object`&&!!e&&(e[p]||!(t=Object.getPrototypeOf(e))||t===Object.prototype||Array.isArray(e))}function vu(e,t=new Set){let n,r,i,a;if(n=e!=null&&e[fu])return n;if(!_u(e)||t.has(e))return e;if(Array.isArray(e)){Object.isFrozen(e)?e=e.slice(0):t.add(e);for(let n=0,a=e.length;n<a;n++)i=e[n],(r=vu(i,t))!==i&&(e[n]=r)}else{Object.isFrozen(e)?e=Object.assign({},e):t.add(e);let n=Object.keys(e),o=Object.getOwnPropertyDescriptors(e);for(let s=0,c=n.length;s<c;s++)a=n[s],!o[a].get&&(i=e[a],(r=vu(i,t))!==i&&(e[a]=r))}return e}function yu(e,t){let n=e[t];return n||Object.defineProperty(e,t,{value:n=Object.create(null)}),n}function bu(e,t,n){if(e[t])return e[t];let[r,i]=k(n,{equals:!1,internal:!0});return r.$=i,e[t]=r}function xu(e,t){let n=Reflect.getOwnPropertyDescriptor(e,t);return!n||n.get||!n.configurable||t===p||t===pu?n:(delete n.value,delete n.writable,n.get=()=>e[p][t],n)}function Su(e){ne()&&bu(yu(e,pu),hu)()}function Cu(e){return Su(e),Reflect.ownKeys(e)}var wu={get(e,t,n){if(t===fu)return e;if(t===p)return n;if(t===m)return Su(e),n;let r=yu(e,pu),i=r[t],a=i?i():e[t];if(t===pu||t===mu||t===`__proto__`)return a;if(!i){let n=Object.getOwnPropertyDescriptor(e,t);ne()&&(typeof a!=`function`||Object.prototype.hasOwnProperty.call(e,t))&&!(n&&n.get)&&(a=bu(r,t,a)())}return _u(a)?gu(a):a},has(e,t){return t===fu||t===p||t===m||t===pu||t===mu||t===`__proto__`?!0:(ne()&&bu(yu(e,mu),t)(),t in e)},set(){return!0},deleteProperty(){return!0},ownKeys:Cu,getOwnPropertyDescriptor:xu};function Tu(e,t,n,r=!1){if(t===`__proto__`||!r&&e[t]===n)return;let i=e[t],a=e.length;n===void 0?(delete e[t],e[mu]&&e[mu][t]&&i!==void 0&&e[mu][t].$()):(e[t]=n,e[mu]&&e[mu][t]&&i===void 0&&e[mu][t].$());let o=yu(e,pu),s;if((s=bu(o,t,i))&&s.$(()=>n),Array.isArray(e)&&e.length!==a){for(let t=e.length;t<a;t++)(s=o[t])&&s.$();(s=bu(o,`length`,a))&&s.$(e.length)}(s=o[hu])&&s.$()}function Eu(e,t){let n=Object.keys(t);for(let r=0;r<n.length;r+=1){let i=n[r];Du(i)||Tu(e,i,t[i])}}function Du(e){return e===`__proto__`||e===`constructor`||e===`prototype`}function Ou(e,t){if(typeof t==`function`&&(t=t(e)),t=vu(t),Array.isArray(t)){if(e===t)return;let n=0,r=t.length;for(;n<r;n++){let r=t[n];e[n]!==r&&Tu(e,n,r)}Tu(e,`length`,r)}else Eu(e,t)}function ku(e,t,n=[]){let r,i=e;if(t.length>1){r=t.shift();let a=typeof r,o=Array.isArray(e);if(a===`string`&&(r===`__proto__`||t.length>1&&Du(r)))return;if(Array.isArray(r)){for(let i=0;i<r.length;i++)ku(e,[r[i]].concat(t),n);return}else if(o&&a===`function`){for(let i=0;i<e.length;i++)r(e[i],i)&&ku(e,[i].concat(t),n);return}else if(o&&a===`object`){let{from:i=0,to:a=e.length-1,by:o=1}=r;for(let r=i;r<=a;r+=o)ku(e,[r].concat(t),n);return}else if(t.length>1){ku(e[r],t,[r].concat(n));return}i=e[r],n=[r].concat(n)}let a=t[0];typeof a==`function`&&(a=a(i,n),a===i)||r===void 0&&a==null||(a=vu(a),r===void 0||_u(i)&&_u(a)&&!Array.isArray(a)?Eu(i,a):Tu(e,r,a))}function Au(...[e,t]){let n=vu(e||{}),r=Array.isArray(n),i=gu(n);function a(...e){F(()=>{r&&e.length===1?Ou(n,e[0]):ku(n,e)})}return[i,a]}var ju=Symbol(`store-root`);function Mu(e){return e===`__proto__`||e===`constructor`||e===`prototype`}function Nu(e,t,n,r,i){if(Mu(n))return;let a=t[n];if(e===a)return;let o=Array.isArray(e);if(n!==ju&&(!_u(e)||!_u(a)||o!==Array.isArray(a)||i&&e[i]!==a[i])){Tu(t,n,e);return}if(o){if(e.length&&a.length&&(!r||i&&e[0]&&e[0][i]!=null)){let t,n,o,s,c,l,u,d;for(o=0,s=Math.min(a.length,e.length);o<s&&(a[o]===e[o]||i&&a[o]&&e[o]&&a[o][i]&&a[o][i]===e[o][i]);o++)Nu(e[o],a,o,r,i);let f=Array(e.length),p=new Map;for(s=a.length-1,c=e.length-1;s>=o&&c>=o&&(a[s]===e[c]||i&&a[s]&&e[c]&&a[s][i]&&a[s][i]===e[c][i]);s--,c--)f[c]=a[s];if(o>c||o>s){for(n=o;n<=c;n++)Tu(a,n,e[n]);for(;n<e.length;n++)Tu(a,n,f[n]),Nu(e[n],a,n,r,i);a.length>e.length&&Tu(a,`length`,e.length);return}for(u=Array(c+1),n=c;n>=o;n--)l=e[n],d=i&&l?l[i]:l,t=p.get(d),u[n]=t===void 0?-1:t,p.set(d,n);for(t=o;t<=s;t++)l=a[t],d=i&&l?l[i]:l,n=p.get(d),n!==void 0&&n!==-1&&(f[n]=a[t],n=u[n],p.set(d,n));for(n=o;n<e.length;n++)n in f?(Tu(a,n,f[n]),Nu(e[n],a,n,r,i)):Tu(a,n,e[n])}else for(let t=0,n=e.length;t<n;t++)Nu(e[t],a,t,r,i);a.length>e.length&&Tu(a,`length`,e.length);return}let s=Object.keys(e);for(let t=0,n=s.length;t<n;t++)Mu(s[t])||Nu(e[s[t]],a,s[t],r,i);let c=Object.keys(a);for(let t=0,n=c.length;t<n;t++)e[c[t]]===void 0&&Tu(a,c[t],void 0)}function Pu(e,t={}){let{merge:n,key:r=`id`}=t,i=vu(e);return e=>{if(!_u(e)||!_u(i))return i;let t=Nu(i,{[ju]:e},ju,n,r);return t===void 0?e:t}}var Fu=new WeakMap,Iu={get(e,t){if(t===fu)return e;let n=e[t];if(t===p||t===m||t===pu||t===mu||t===`__proto__`)return n;let r;return _u(n)?Fu.get(n)||(Fu.set(n,r=new Proxy(n,Iu)),r):n},set(e,t,n){return Tu(e,t,vu(n)),!0},deleteProperty(e,t){return Tu(e,t,void 0,!0),!0}};function Lu(e){return t=>{if(_u(t)){let n;(n=Fu.get(t))||Fu.set(t,n=new Proxy(t,Iu)),e(n)}return t}}function Ru(){let[e,...t]=window.location.hash.replace(/^#\/?/,``).split(`/`);return e===`session`&&t[0]?t[1]===`subagent`&&t[2]?{screen:`subagent`,key:t[0],agentId:t[2]}:{screen:`session`,key:t[0]}:e===`files`?{screen:`files`,path:t.length?decodeURIComponent(t.join(`/`)):void 0}:e===`settings`?{screen:`settings`}:e===`pairing`?{screen:`pairing`}:{screen:`fleet`}}function zu(e){switch(e.screen){case`fleet`:return`#/`;case`session`:return`#/session/${e.key}`;case`subagent`:return`#/session/${e.key}/subagent/${e.agentId}`;case`files`:return e.path?`#/files/${encodeURIComponent(e.path)}`:`#/files`;case`settings`:return`#/settings`;case`pairing`:return`#/pairing`}}function Bu(){let[e,t]=Au({}),[n,r]=Au({}),[i,a]=k(Ru()),[o,s]=k({runtimes:[],diskSessions:[]}),[c,l]=k(),[u,d]=k(),[f,p]=k(!1),[m,h]=k([]),g=new Map,_=0,v=0;window.addEventListener(`hashchange`,()=>a(Ru()));function y(e){window.location.hash=zu(e)}function b(e){return n[e]??0}function x(e){return g.get(e)??0}function S(e){g.set(e,x(e)+1)}function C(e){return{revision:b(e),generation:x(e),epoch:_}}function w(e,t){return b(e)===t.revision&&x(e)===t.generation&&_===t.epoch}function T(e){r(e,b(e)+1)}function E(n){e[n]||t(n,Xl(n))}function D(e,n){E(e),t(e,Lu(n)),T(e)}function O(e){S(e),t(e,void 0),r(e,void 0),ms(e)}function A(){let e=Ru();return e.screen===`session`||e.screen===`subagent`?e.key:void 0}function j(e,t=`info`){--v,h(n=>[...n,{id:v,text:e,tone:t}].slice(-20))}async function M(){try{s(await X.fleet()),l(void 0)}catch(e){throw l(e instanceof Error?e.message:String(e)),e}}function N(e){let n=e.event?.type;if(n===`dashboard_resync`)_+=1,t(Pu({})),r(Pu({}));else if(n===`runtime_removed`){if(e.key){let t=A()===e.key;O(e.key),t&&(j(`session ${e.key} was stopped`,`warning`),y({screen:`fleet`}))}}else e.key&&D(e.key,t=>uu(t,e.event));(n===`agent_start`||n===`agent_end`||n===`background_agent_start`||n===`background_agent_end`||n===`runtime_removed`)&&M().catch(()=>{})}async function P(){await M().catch(()=>{});let e=i();try{e.screen===`session`?await L(e.key):e.screen===`subagent`&&(await L(e.key),await ne(e.key,e.agentId))}catch{}}let ee;async function F(){try{let e=await X.auth();d(e)}catch(e){d({mode:`remote`,needsPairing:e?.body?.needsPairing??!1,identity:e?.body?.identity,error:e?.message}),y({screen:`pairing`});return}await M().catch(()=>{}),ee=ct({onEnvelope:N,onStatusChange:p,onResync:()=>{P().catch(()=>{})}})}function I(){ee?.()}function te(t){if(m().some(e=>e.id===t)){h(e=>e.filter(e=>e.id!==t));return}for(let[n,r]of Object.entries(e))if(r.toasts.some(e=>e.id===t)){D(n,e=>du(e,t));return}}async function L(e,t){let n=C(e),[r,i,a]=await Promise.allSettled([X.messages(e,t),X.backgroundAgents(e,t),X.runtime(e,t)]);!t?.aborted&&w(e,n)&&D(e,e=>{if(r.status===`fulfilled`&&(e.entries=eu(r.value.messages)),i.status===`fulfilled`){for(let t of i.value.agents)e.backgroundAgents[t.agentId]=t;au(e)}if(a.status===`fulfilled`&&a.value?.state){let t=a.value.state;e.streaming=t.isStreaming,e.compacting=t.isCompacting,t.isStreaming&&!e.workingSince?(e.workingSince=Date.now(),e.workingText=e.workingText??`working`):t.isStreaming||(e.workingSince=void 0)}});let o=[r,i,a].find(e=>e.status===`rejected`);if(o?.status===`rejected`)throw o.reason instanceof Error?o.reason:Error(String(o.reason))}async function ne(e,t,n){let r=C(e),{agent:i,messages:a}=await X.subagentMessages(e,t,n);n?.aborted||!w(e,r)||D(e,e=>{e.backgroundAgents[t]=i;let n=e.subagents[t];n||(n={agentId:t,entries:[],streaming:i.status===`running`},e.subagents[t]=n),a.length>0&&(n.entries=eu(a)),n.streaming=i.status===`running`})}return{sessions:e,revisions:n,route:i,navigate:y,fleet:o,fleetError:c,refreshFleet:M,auth:u,connected:f,notices:m,start:F,stop:I,dismissToast:te,hydrateSession:L,hydrateSubagent:ne}}function Vu(){let e=Bu();te(()=>{e.start(),`serviceWorker`in navigator&&(navigator.serviceWorker.addEventListener(`message`,t=>{t.data?.type===`navigate-session`&&typeof t.data.sessionKey==`string`&&e.navigate({screen:`session`,key:t.data.sessionKey})}),navigator.serviceWorker.register(`./sw.js`).catch(e=>{console.warn(`dashboard: service worker registration failed`,e)}))}),L(()=>e.stop());let t=new Set;M(()=>{let n=new Map;for(let t of e.fleet().runtimes)t.needsAttention&&n.set(t.key,{name:t.state.sessionName??t.state.sessionId.slice(0,8),reason:`needs attention`});for(let t of Object.values(e.sessions))t.needsAttention&&n.set(t.key,{name:t.sessionName??t.title??t.key,reason:t.uiRequests[0]?.title?`waiting for input — ${t.uiRequests[0].title}`:t.statusEntries.find(e=>e.tone===`error`)?.text??`needs attention`});let r=n.size>0,i=e.route(),a=i.screen===`session`||i.screen===`subagent`?i.key:void 0,o=a?e.sessions[a]:void 0,s=a?e.fleet().runtimes.find(e=>e.key===a):void 0,c=o?.title??o?.sessionName??s?.state.sessionName,l=c?`${c} — dreb`:`dreb`;document.title=r?`◆ ${l}`:l;for(let[e,r]of n)t.has(e)||(t.add(e),typeof Notification<`u`&&Notification.permission===`granted`&&document.visibilityState!==`visible`&&`serviceWorker`in navigator&&navigator.serviceWorker.ready.then(t=>t.showNotification(`dreb — ${r.name}`,{body:r.reason,tag:e,data:{sessionKey:e}})).catch(e=>console.warn(`dashboard: showNotification failed`,e)));for(let e of[...t])n.has(e)||t.delete(e)});let n=()=>[...Object.values(e.sessions).flatMap(e=>e.toasts.map(t=>({...t,sessionKey:e.key}))),...e.notices()].slice(-5);return[V(Oe,{get fallback(){return V(on,{store:e})},get children(){return[V(W,{get when(){return e.route().screen===`session`},get children(){return V(Jc,{store:e,get sessionKey(){return e.route().key}})}}),V(W,{get when(){return e.route().screen===`subagent`},get children(){return V(Yl,{store:e,get sessionKey(){return e.route().key},get agentId(){return e.route().agentId}})}}),V(W,{get when(){return e.route().screen===`files`},get children(){return V(wt,{store:e,get initialPath(){return e.route().path}})}}),V(W,{get when(){return e.route().screen===`settings`},get children(){return V(Wl,{store:e})}}),V(W,{get when(){return e.route().screen===`pairing`},get children(){return V(fn,{store:e})}})]}}),V(rt,{get toasts(){return n()},onDismiss:t=>e.dismissToast(t)})]}var Hu=document.getElementById(`root`);if(!Hu)throw Error(`dashboard: #root element missing from index.html`);je(()=>V(Vu,{}),Hu);
|
|
77
|
+
`).trim()||a),{kind:`agent-result`,text:o,header:a,raw:e,timestamp:t}}function eu(e){let t=Ql(e.content);return $l(t,e.timestamp)??{kind:`user`,text:t,timestamp:e.timestamp}}function tu(e){let t=[],n=new Map;for(let r of e)if(r.role===`user`)t.push(eu(r));else if(r.role===`assistant`){let e=[],i=()=>{e.length!==0&&(t.push({kind:`assistant`,blocks:e,model:r.model,streaming:!1,timestamp:r.timestamp}),e=[])};if(Array.isArray(r.content)){for(let a of r.content)if(a.type===`text`&&typeof a.text==`string`)e.push({kind:`text`,text:a.text});else if(a.type===`thinking`&&typeof a.thinking==`string`)e.push({kind:`thinking`,text:a.thinking});else if(a.type===`toolCall`){i();let e=a,o={kind:`tool`,toolCallId:String(e.id??``),toolName:String(e.name??`tool`),args:e.arguments,status:`running`,resultText:``,startedAt:r.timestamp??Date.now()};n.set(o.toolCallId,o),t.push(o)}}i()}else if(r.role===`toolResult`){let e=n.get(String(r.toolCallId));e&&(e.status=r.isError?`error`:`done`,e.resultText=Ql(r.content),e.details=r.details,e.endedAt=r.timestamp)}else if(r.role===`bashExecution`){let e=r;t.push({kind:`tool`,toolCallId:`bash-exec-${t.length}`,toolName:`bash (user)`,args:{command:e.command},status:`done`,resultText:e.output??``,startedAt:r.timestamp??Date.now()})}else if(r.role===`custom`){let e=r;t.push({kind:`custom`,tag:e.customType??`extension`,text:e.displayText??Ql(r.content)})}return t}var nu=0,ru=20;function iu(e){let t=e.toasts.length-ru;t>0&&e.toasts.splice(0,t)}function au(e){let t=Date.parse(e.startedAt);return Number.isFinite(t)?t:0}function ou(e){let t=Object.values(e.backgroundAgents).filter(e=>e.status!==`running`).sort((e,t)=>au(e)-au(t)||e.agentId.localeCompare(t.agentId)),n=t.length-20;if(!(n<=0))for(let r of t.slice(0,n))delete e.backgroundAgents[r.agentId],delete e.subagents[r.agentId]}function su(e){e.needsAttention=e.uiRequests.length>0||e.statusEntries.some(e=>e.tone===`error`)||!!e.suggestedCommand}function cu(e){for(let t=e.entries.length-1;t>=0;t--){let n=e.entries[t];if(n.kind===`assistant`)return n}}function lu(e,t){for(let n=e.entries.length-1;n>=0;n--){let r=e.entries[n];if(r.kind===`tool`&&r.toolCallId===t)return r}}function uu(e,t){switch(t.type){case`agent_start`:e.streaming=!0;break;case`agent_end`:{e.streaming=!1;let t=cu(e);t&&(t.streaming=!1);break}case`message_start`:{let n=t.message;n?.role===`user`?e.entries.push(eu(n)):n?.role===`assistant`&&e.entries.push({kind:`assistant`,blocks:[],model:n.model,streaming:!0,timestamp:n.timestamp});break}case`message_update`:{let n=t.assistantMessageEvent;if(!n)break;let r=cu(e);if((!r||!r.streaming)&&(r={kind:`assistant`,blocks:[],streaming:!0},e.entries.push(r)),n.type===`text_start`)r.blocks.push({kind:`text`,text:``});else if(n.type===`thinking_start`)r.blocks.push({kind:`thinking`,text:``});else if(n.type===`text_delta`||n.type===`thinking_delta`){let e=r.blocks[r.blocks.length-1];e&&(e.text+=n.delta??``)}else if(n.type===`text_end`||n.type===`thinking_end`){let e=r.blocks[r.blocks.length-1];e&&typeof n.content==`string`&&(e.text=n.content)}break}case`message_end`:{let n=t.message;if(n?.role===`assistant`){let t=cu(e);if(t?.streaming){let e=tu([n]).find(e=>e.kind===`assistant`);e&&(t.blocks=e.blocks,t.model=e.model),t.streaming=!1}else t||e.entries.push(...tu([n]))}else if(n?.role===`user`){let t=e.entries[e.entries.length-1],r=eu(n);r.kind===`agent-result`?t?.kind===`agent-result`&&t.raw===r.raw||e.entries.push(r):t?.kind===`user`&&t.text===r.text||e.entries.push(r)}break}case`tool_execution_start`:e.entries.push({kind:`tool`,toolCallId:String(t.toolCallId),toolName:String(t.toolName),args:t.args,status:`running`,resultText:``,startedAt:Date.now()});break;case`tool_execution_update`:{let n=lu(e,String(t.toolCallId));if(n){let e=t.partialResult;n.resultText=typeof e==`string`?e:e?.content?Ql(e.content):n.resultText}break}case`tool_execution_end`:{let n=lu(e,String(t.toolCallId));if(n){n.status=t.isError?`error`:`done`;let e=t.result;n.resultText=typeof e==`string`?e:e?.content?Ql(e.content):n.resultText,n.details=t.result?.details,n.endedAt=Date.now()}break}default:break}}function du(e,t){switch(uu(e,t),t.type){case`agent_start`:e.workingText=`working`,e.workingSince=Date.now(),e.lastError=void 0,e.suggestedCommand=void 0,e.uiRequests=[];break;case`agent_end`:e.workingText=void 0,e.workingSince=void 0,e.statusEntries=e.statusEntries.filter(e=>e.key!==`retry`&&e.key!==`paused`);break;case`tool_execution_start`:e.workingText=`${t.toolName}`;break;case`auto_compaction_start`:e.compacting=!0,e.statusEntries.push({key:`compaction`,text:`compacting context…`,tone:`info`});break;case`auto_compaction_end`:{e.compacting=!1,e.statusEntries=e.statusEntries.filter(e=>e.key!==`compaction`);let n=t.result;n&&!t.aborted&&e.entries.push({kind:`summary`,label:`compaction`,text:n.summary??`context compacted`,tokensBefore:n.tokensBefore}),t.errorMessage&&e.statusEntries.push({key:`compaction-error`,text:String(t.errorMessage),tone:`error`});break}case`auto_retry_start`:e.statusEntries=e.statusEntries.filter(e=>e.key!==`retry`),e.statusEntries.push({key:`retry`,text:`retrying (${t.attempt}/${t.maxAttempts}) — ${t.errorMessage}`,tone:`warning`});break;case`auto_retry_end`:e.statusEntries=e.statusEntries.filter(e=>e.key!==`retry`),!t.success&&t.finalError&&(e.statusEntries.push({key:`error`,text:String(t.finalError),tone:`error`}),e.lastError=String(t.finalError));break;case`stream_retry`:{e.statusEntries=e.statusEntries.filter(e=>e.key!==`retry`),e.statusEntries.push({key:`retry`,text:`stream dropped, retrying (${t.attempt}/${t.maxAttempts})`,tone:`warning`});let n=cu(e);n?.streaming&&e.entries.splice(e.entries.indexOf(n),1);break}case`length_retry`:{e.statusEntries=e.statusEntries.filter(e=>e.key!==`retry`),e.statusEntries.push({key:`retry`,text:`response truncated, retrying with larger budget (${t.attempt}/${t.maxAttempts})`,tone:`warning`});let n=cu(e);n?.streaming&&e.entries.splice(e.entries.indexOf(n),1);break}case`tasks_update`:e.tasks=t.tasks??[];break;case`suggest_next`:e.suggestedCommand=String(t.command);break;case`session_name_changed`:e.sessionName=String(t.name??``);break;case`background_agent_start`:e.backgroundAgents[String(t.agentId)]={agentId:String(t.agentId),agentType:String(t.agentType),taskSummary:String(t.taskSummary),startedAt:new Date().toISOString(),status:`running`,sessionDir:t.sessionDir};break;case`background_agent_end`:{let n=e.backgroundAgents[String(t.agentId)];n&&(n.status=t.success?`completed`:`failed`,n.sessionFile=t.sessionFile??n.sessionFile);let r=e.subagents[String(t.agentId)];r&&(r.streaming=!1),ou(e);break}case`background_agent_event`:{let n=String(t.agentId),r=e.subagents[n];r||(r={agentId:n,entries:[],streaming:!0},e.subagents[n]=r);let i=t.event;if(i?.type===`session`)break;i?.type===`agent_start`&&i.model&&(r.model=i.model.id),uu(r,i);break}case`parent_paused_for_background_agents`:e.statusEntries=e.statusEntries.filter(e=>e.key!==`paused`),e.statusEntries.push({key:`paused`,text:`paused — waiting on ${t.runningAgentCount} background agent${t.runningAgentCount===1?``:`s`}`,tone:`warning`});break;case`extension_ui_request`:{let n=t.method;if(n===`select`||n===`confirm`||n===`input`||n===`editor`)e.uiRequests.push({id:String(t.id),method:n,title:String(t.title??``),message:t.message,options:t.options,prefill:t.prefill,placeholder:t.placeholder});else if(n===`notify`){nu+=1;let n=t.notifyType??`info`;e.toasts.push({id:nu,text:String(t.message??``),tone:n}),iu(e)}else if(n===`setStatus`){let n=`ext:${t.statusKey??`default`}`;e.statusEntries=e.statusEntries.filter(e=>e.key!==n),t.statusText&&e.statusEntries.push({key:n,text:String(t.statusText),tone:`info`})}else if(n===`setWidget`){let n=t.widgetPlacement===`belowEditor`?`below`:`above`;e.widgets[n]=Array.isArray(t.widgetLines)?t.widgetLines:[]}else n===`setTitle`?e.title=String(t.title??``):n===`set_editor_text`&&(e.composerPrefill=String(t.text??``));break}case`extension_error`:nu+=1,e.toasts.push({id:nu,text:`extension error: ${t.error}`,tone:`error`}),iu(e);break;default:break}su(e)}function fu(e,t){e.toasts=e.toasts.filter(e=>e.id!==t)}var pu=Symbol(`store-raw`),mu=Symbol(`store-node`),hu=Symbol(`store-has`),gu=Symbol(`store-self`);function _u(e){let t=e[p];if(!t&&(Object.defineProperty(e,p,{value:t=new Proxy(e,Tu)}),!Array.isArray(e))){let n=Object.keys(e),r=Object.getOwnPropertyDescriptors(e),i=Object.getPrototypeOf(e),a=i!==null&&typeof e==`object`&&!!e&&!Array.isArray(e)&&i!==Object.prototype;if(a){let e=Object.getOwnPropertyDescriptors(i);n.push(...Object.keys(e)),Object.assign(r,e)}for(let i=0,o=n.length;i<o;i++){let o=n[i];a&&o===`constructor`||r[o].get&&Object.defineProperty(e,o,{configurable:!0,enumerable:r[o].enumerable,get:r[o].get.bind(t)})}}return t}function vu(e){let t;return typeof e==`object`&&!!e&&(e[p]||!(t=Object.getPrototypeOf(e))||t===Object.prototype||Array.isArray(e))}function yu(e,t=new Set){let n,r,i,a;if(n=e!=null&&e[pu])return n;if(!vu(e)||t.has(e))return e;if(Array.isArray(e)){Object.isFrozen(e)?e=e.slice(0):t.add(e);for(let n=0,a=e.length;n<a;n++)i=e[n],(r=yu(i,t))!==i&&(e[n]=r)}else{Object.isFrozen(e)?e=Object.assign({},e):t.add(e);let n=Object.keys(e),o=Object.getOwnPropertyDescriptors(e);for(let s=0,c=n.length;s<c;s++)a=n[s],!o[a].get&&(i=e[a],(r=yu(i,t))!==i&&(e[a]=r))}return e}function bu(e,t){let n=e[t];return n||Object.defineProperty(e,t,{value:n=Object.create(null)}),n}function xu(e,t,n){if(e[t])return e[t];let[r,i]=k(n,{equals:!1,internal:!0});return r.$=i,e[t]=r}function Su(e,t){let n=Reflect.getOwnPropertyDescriptor(e,t);return!n||n.get||!n.configurable||t===p||t===mu?n:(delete n.value,delete n.writable,n.get=()=>e[p][t],n)}function Cu(e){ne()&&xu(bu(e,mu),gu)()}function wu(e){return Cu(e),Reflect.ownKeys(e)}var Tu={get(e,t,n){if(t===pu)return e;if(t===p)return n;if(t===m)return Cu(e),n;let r=bu(e,mu),i=r[t],a=i?i():e[t];if(t===mu||t===hu||t===`__proto__`)return a;if(!i){let n=Object.getOwnPropertyDescriptor(e,t);ne()&&(typeof a!=`function`||Object.prototype.hasOwnProperty.call(e,t))&&!(n&&n.get)&&(a=xu(r,t,a)())}return vu(a)?_u(a):a},has(e,t){return t===pu||t===p||t===m||t===mu||t===hu||t===`__proto__`?!0:(ne()&&xu(bu(e,hu),t)(),t in e)},set(){return!0},deleteProperty(){return!0},ownKeys:wu,getOwnPropertyDescriptor:Su};function Eu(e,t,n,r=!1){if(t===`__proto__`||!r&&e[t]===n)return;let i=e[t],a=e.length;n===void 0?(delete e[t],e[hu]&&e[hu][t]&&i!==void 0&&e[hu][t].$()):(e[t]=n,e[hu]&&e[hu][t]&&i===void 0&&e[hu][t].$());let o=bu(e,mu),s;if((s=xu(o,t,i))&&s.$(()=>n),Array.isArray(e)&&e.length!==a){for(let t=e.length;t<a;t++)(s=o[t])&&s.$();(s=xu(o,`length`,a))&&s.$(e.length)}(s=o[gu])&&s.$()}function Du(e,t){let n=Object.keys(t);for(let r=0;r<n.length;r+=1){let i=n[r];Ou(i)||Eu(e,i,t[i])}}function Ou(e){return e===`__proto__`||e===`constructor`||e===`prototype`}function ku(e,t){if(typeof t==`function`&&(t=t(e)),t=yu(t),Array.isArray(t)){if(e===t)return;let n=0,r=t.length;for(;n<r;n++){let r=t[n];e[n]!==r&&Eu(e,n,r)}Eu(e,`length`,r)}else Du(e,t)}function Au(e,t,n=[]){let r,i=e;if(t.length>1){r=t.shift();let a=typeof r,o=Array.isArray(e);if(a===`string`&&(r===`__proto__`||t.length>1&&Ou(r)))return;if(Array.isArray(r)){for(let i=0;i<r.length;i++)Au(e,[r[i]].concat(t),n);return}else if(o&&a===`function`){for(let i=0;i<e.length;i++)r(e[i],i)&&Au(e,[i].concat(t),n);return}else if(o&&a===`object`){let{from:i=0,to:a=e.length-1,by:o=1}=r;for(let r=i;r<=a;r+=o)Au(e,[r].concat(t),n);return}else if(t.length>1){Au(e[r],t,[r].concat(n));return}i=e[r],n=[r].concat(n)}let a=t[0];typeof a==`function`&&(a=a(i,n),a===i)||r===void 0&&a==null||(a=yu(a),r===void 0||vu(i)&&vu(a)&&!Array.isArray(a)?Du(i,a):Eu(e,r,a))}function ju(...[e,t]){let n=yu(e||{}),r=Array.isArray(n),i=_u(n);function a(...e){F(()=>{r&&e.length===1?ku(n,e[0]):Au(n,e)})}return[i,a]}var Mu=Symbol(`store-root`);function Nu(e){return e===`__proto__`||e===`constructor`||e===`prototype`}function Pu(e,t,n,r,i){if(Nu(n))return;let a=t[n];if(e===a)return;let o=Array.isArray(e);if(n!==Mu&&(!vu(e)||!vu(a)||o!==Array.isArray(a)||i&&e[i]!==a[i])){Eu(t,n,e);return}if(o){if(e.length&&a.length&&(!r||i&&e[0]&&e[0][i]!=null)){let t,n,o,s,c,l,u,d;for(o=0,s=Math.min(a.length,e.length);o<s&&(a[o]===e[o]||i&&a[o]&&e[o]&&a[o][i]&&a[o][i]===e[o][i]);o++)Pu(e[o],a,o,r,i);let f=Array(e.length),p=new Map;for(s=a.length-1,c=e.length-1;s>=o&&c>=o&&(a[s]===e[c]||i&&a[s]&&e[c]&&a[s][i]&&a[s][i]===e[c][i]);s--,c--)f[c]=a[s];if(o>c||o>s){for(n=o;n<=c;n++)Eu(a,n,e[n]);for(;n<e.length;n++)Eu(a,n,f[n]),Pu(e[n],a,n,r,i);a.length>e.length&&Eu(a,`length`,e.length);return}for(u=Array(c+1),n=c;n>=o;n--)l=e[n],d=i&&l?l[i]:l,t=p.get(d),u[n]=t===void 0?-1:t,p.set(d,n);for(t=o;t<=s;t++)l=a[t],d=i&&l?l[i]:l,n=p.get(d),n!==void 0&&n!==-1&&(f[n]=a[t],n=u[n],p.set(d,n));for(n=o;n<e.length;n++)n in f?(Eu(a,n,f[n]),Pu(e[n],a,n,r,i)):Eu(a,n,e[n])}else for(let t=0,n=e.length;t<n;t++)Pu(e[t],a,t,r,i);a.length>e.length&&Eu(a,`length`,e.length);return}let s=Object.keys(e);for(let t=0,n=s.length;t<n;t++)Nu(s[t])||Pu(e[s[t]],a,s[t],r,i);let c=Object.keys(a);for(let t=0,n=c.length;t<n;t++)e[c[t]]===void 0&&Eu(a,c[t],void 0)}function Fu(e,t={}){let{merge:n,key:r=`id`}=t,i=yu(e);return e=>{if(!vu(e)||!vu(i))return i;let t=Pu(i,{[Mu]:e},Mu,n,r);return t===void 0?e:t}}var Iu=new WeakMap,Lu={get(e,t){if(t===pu)return e;let n=e[t];if(t===p||t===m||t===mu||t===hu||t===`__proto__`)return n;let r;return vu(n)?Iu.get(n)||(Iu.set(n,r=new Proxy(n,Lu)),r):n},set(e,t,n){return Eu(e,t,yu(n)),!0},deleteProperty(e,t){return Eu(e,t,void 0,!0),!0}};function Ru(e){return t=>{if(vu(t)){let n;(n=Iu.get(t))||Iu.set(t,n=new Proxy(t,Lu)),e(n)}return t}}function zu(){let[e,...t]=window.location.hash.replace(/^#\/?/,``).split(`/`);return e===`session`&&t[0]?t[1]===`subagent`&&t[2]?{screen:`subagent`,key:t[0],agentId:t[2]}:{screen:`session`,key:t[0]}:e===`files`?{screen:`files`,path:t.length?decodeURIComponent(t.join(`/`)):void 0}:e===`settings`?{screen:`settings`}:e===`pairing`?{screen:`pairing`}:{screen:`fleet`}}function Bu(e){switch(e.screen){case`fleet`:return`#/`;case`session`:return`#/session/${e.key}`;case`subagent`:return`#/session/${e.key}/subagent/${e.agentId}`;case`files`:return e.path?`#/files/${encodeURIComponent(e.path)}`:`#/files`;case`settings`:return`#/settings`;case`pairing`:return`#/pairing`}}function Vu(){let[e,t]=ju({}),[n,r]=ju({}),[i,a]=k(zu()),[o,s]=k({runtimes:[],diskSessions:[]}),[c,l]=k(),[u,d]=k(),[f,p]=k(!1),[m,h]=k([]),g=new Map,_=0,v=0;window.addEventListener(`hashchange`,()=>a(zu()));function y(e){window.location.hash=Bu(e)}function b(e){return n[e]??0}function x(e){return g.get(e)??0}function S(e){g.set(e,x(e)+1)}function C(e){return{revision:b(e),generation:x(e),epoch:_}}function w(e,t){return b(e)===t.revision&&x(e)===t.generation&&_===t.epoch}function T(e){r(e,b(e)+1)}function E(n){e[n]||t(n,Zl(n))}function D(e,n){E(e),t(e,Ru(n)),T(e)}function O(e){S(e),t(e,void 0),r(e,void 0),ms(e)}function A(){let e=zu();return e.screen===`session`||e.screen===`subagent`?e.key:void 0}function j(e,t=`info`){--v,h(n=>[...n,{id:v,text:e,tone:t}].slice(-20))}async function M(){try{s(await X.fleet()),l(void 0)}catch(e){throw l(e instanceof Error?e.message:String(e)),e}}function N(e){let n=e.event?.type;if(n===`dashboard_resync`)_+=1,t(Fu({})),r(Fu({}));else if(n===`runtime_removed`){if(e.key){let t=A()===e.key;O(e.key),t&&(j(`session ${e.key} was stopped`,`warning`),y({screen:`fleet`}))}}else e.key&&D(e.key,t=>du(t,e.event));(n===`agent_start`||n===`agent_end`||n===`background_agent_start`||n===`background_agent_end`||n===`runtime_removed`)&&M().catch(()=>{})}async function P(){await M().catch(()=>{});let e=i();try{e.screen===`session`?await L(e.key):e.screen===`subagent`&&(await L(e.key),await ne(e.key,e.agentId))}catch{}}let ee;async function F(){try{let e=await X.auth();d(e)}catch(e){d({mode:`remote`,needsPairing:e?.body?.needsPairing??!1,identity:e?.body?.identity,error:e?.message}),y({screen:`pairing`});return}await M().catch(()=>{}),ee=ct({onEnvelope:N,onStatusChange:p,onResync:()=>{P().catch(()=>{})}})}function I(){ee?.()}function te(t){if(m().some(e=>e.id===t)){h(e=>e.filter(e=>e.id!==t));return}for(let[n,r]of Object.entries(e))if(r.toasts.some(e=>e.id===t)){D(n,e=>fu(e,t));return}}async function L(e,t){let n=C(e),[r,i,a]=await Promise.allSettled([X.messages(e,t),X.backgroundAgents(e,t),X.runtime(e,t)]);!t?.aborted&&w(e,n)&&D(e,e=>{if(r.status===`fulfilled`&&(e.entries=tu(r.value.messages)),i.status===`fulfilled`){for(let t of i.value.agents)e.backgroundAgents[t.agentId]=t;ou(e)}if(a.status===`fulfilled`&&a.value?.state){let t=a.value.state;e.streaming=t.isStreaming,e.compacting=t.isCompacting,t.isStreaming&&!e.workingSince?(e.workingSince=Date.now(),e.workingText=e.workingText??`working`):t.isStreaming||(e.workingSince=void 0)}});let o=[r,i,a].find(e=>e.status===`rejected`);if(o?.status===`rejected`)throw o.reason instanceof Error?o.reason:Error(String(o.reason))}async function ne(e,t,n){let r=C(e),{agent:i,messages:a}=await X.subagentMessages(e,t,n);n?.aborted||!w(e,r)||D(e,e=>{e.backgroundAgents[t]=i;let n=e.subagents[t];n||(n={agentId:t,entries:[],streaming:i.status===`running`},e.subagents[t]=n),a.length>0&&(n.entries=tu(a)),n.streaming=i.status===`running`})}return{sessions:e,revisions:n,route:i,navigate:y,fleet:o,fleetError:c,refreshFleet:M,auth:u,connected:f,notices:m,start:F,stop:I,dismissToast:te,hydrateSession:L,hydrateSubagent:ne}}var Hu=5e3;function Uu(e){let t;return Promise.race([e,new Promise((e,n)=>{t=setTimeout(()=>n(Error(`service worker not ready`)),Hu)})]).finally(()=>{t&&clearTimeout(t)})}function Wu(){let e=Vu();te(()=>{e.start(),`serviceWorker`in navigator&&(navigator.serviceWorker.addEventListener(`message`,t=>{if(t.data?.type===`navigate-session`&&typeof t.data.sessionKey==`string`){let n=t.data.sessionKey,r=!!e.sessions[n]||e.fleet().runtimes.some(e=>e.key===n);e.navigate(r?{screen:`session`,key:n}:{screen:`fleet`})}}),navigator.serviceWorker.register(`./sw.js`).catch(e=>{console.warn(`dashboard: service worker registration failed`,e)}))}),L(()=>e.stop());let t=new Set,n=new Set,r=t=>!!(e.fleet().runtimes.find(e=>e.key===t)?.needsAttention||e.sessions[t]?.needsAttention);M(()=>{let i=new Map;for(let t of e.fleet().runtimes)t.needsAttention&&i.set(t.key,{name:t.state.sessionName??t.state.sessionId.slice(0,8),reason:`needs attention`});for(let t of Object.values(e.sessions))t.needsAttention&&i.set(t.key,{name:t.sessionName??t.title??t.key,reason:t.uiRequests[0]?.title?`waiting for input — ${t.uiRequests[0].title}`:t.statusEntries.find(e=>e.tone===`error`)?.text??`needs attention`});let a=i.size>0,o=e.route(),s=o.screen===`session`||o.screen===`subagent`?o.key:void 0,c=s?e.sessions[s]:void 0,l=s?e.fleet().runtimes.find(e=>e.key===s):void 0,u=c?.title??c?.sessionName??l?.state.sessionName,d=u?`${u} — dreb`:`dreb`;document.title=a?`◆ ${d}`:d;for(let[e,a]of i)n.has(e)||t.has(e)||typeof Notification<`u`&&Notification.permission===`granted`&&document.visibilityState!==`visible`&&`serviceWorker`in navigator&&(t.add(e),Uu(navigator.serviceWorker.ready).then(t=>t.showNotification(`dreb — ${a.name}`,{body:a.reason,tag:e,data:{sessionKey:e}})).then(()=>{t.delete(e),r(e)?n.add(e):n.delete(e)}).catch(n=>{t.delete(e),console.warn(`dashboard: showNotification failed`,n)}));for(let e of[...n])i.has(e)||n.delete(e);for(let e of[...t])i.has(e)||t.delete(e)});let i=()=>[...Object.values(e.sessions).flatMap(e=>e.toasts.map(t=>({...t,sessionKey:e.key}))),...e.notices()].slice(-5);return[V(Oe,{get fallback(){return V(on,{store:e})},get children(){return[V(W,{get when(){return e.route().screen===`session`},get children(){return V(Jc,{store:e,get sessionKey(){return e.route().key}})}}),V(W,{get when(){return e.route().screen===`subagent`},get children(){return V(Xl,{store:e,get sessionKey(){return e.route().key},get agentId(){return e.route().agentId}})}}),V(W,{get when(){return e.route().screen===`files`},get children(){return V(wt,{store:e,get initialPath(){return e.route().path}})}}),V(W,{get when(){return e.route().screen===`settings`},get children(){return V(Gl,{store:e})}}),V(W,{get when(){return e.route().screen===`pairing`},get children(){return V(fn,{store:e})}})]}}),V(rt,{get toasts(){return i()},onDismiss:t=>e.dismissToast(t)})]}var Gu=document.getElementById(`root`);if(!Gu)throw Error(`dashboard: #root element missing from index.html`);je(()=>V(Wu,{}),Gu);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--bg:#fff;--text:#000;--border:#e0e0e0;--muted:#666;--status-running:#00871f;--status-attention:#8a5400;--status-idle:#666;--status-error:#d10041;--mono-font:"IBM Plex Mono", "Courier New", monospace;--fs-small:12px;--fs-secondary:13px;--fs-body:14px;--fs-h3:16px;--fs-h2:18px;--fs-h1:24px;--space-1:4px;--space-2:8px;--space-3:12px;--space-4:16px;--space-5:24px;--space-6:32px;--space-7:48px;--radius:3px;--hairline:1px solid var(--border)}@media (prefers-color-scheme:dark){:root{--bg:#000;--text:#fff;--border:#333;--muted:#999;--status-running:#00e436;--status-attention:#ffa300;--status-idle:#999;--status-error:#ff004d}}*{box-sizing:border-box;margin:0;padding:0}html{-webkit-text-size-adjust:100%}img,svg{max-width:100%;display:block}button,input,textarea,select{font:inherit;color:inherit}body{background:var(--bg);color:var(--text);font-family:var(--mono-font);font-size:var(--fs-body);overflow-wrap:break-word;line-height:1.6;overflow-x:hidden}h1{font-size:var(--fs-h1);font-weight:500}h2{font-size:var(--fs-h2);font-weight:500}h3{font-size:var(--fs-h3);font-weight:500}a{color:var(--text);cursor:pointer}a:hover{color:var(--muted)}code,pre{font-family:var(--mono-font);font-size:var(--fs-secondary)}pre{border:var(--hairline);border-radius:var(--radius);padding:var(--space-3);white-space:pre-wrap;word-break:break-word;overflow-x:auto}::placeholder{color:var(--muted);opacity:1}:focus-visible{outline:1px solid var(--text);outline-offset:2px}.muted{color:var(--muted)}.small{font-size:var(--fs-small)}.container{width:100%;max-width:1200px;padding:var(--space-4) var(--space-5);margin:0 auto}.topbar{border-bottom:var(--hairline);padding-top:env(safe-area-inset-top,0px)}.topbar-inner{max-width:1200px;padding:var(--space-3) var(--space-5);align-items:center;gap:var(--space-4);flex-wrap:wrap;margin:0 auto;display:flex}.wordmark{font-size:var(--fs-h2);letter-spacing:.04em;font-weight:600;text-decoration:none}.topbar nav{gap:var(--space-4);align-items:center;display:flex}.topbar nav a{padding:var(--space-1) 0;border-bottom:1px solid #0000;text-decoration:none}.topbar nav a:hover{color:var(--text);border-bottom-color:var(--text)}.topbar nav a[aria-current=page]{border-bottom-color:var(--text)}.topbar-spacer{margin-left:auto}footer{border-top:var(--hairline);margin-top:var(--space-7);padding:var(--space-4) 0;font-size:var(--fs-secondary);color:var(--muted)}.btn{align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-4);border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--text);cursor:pointer;font-size:var(--fs-body);text-decoration:none;transition:border-color .2s;display:inline-flex}.btn:hover{border-color:var(--text);color:var(--text)}.btn-primary{background:var(--text);color:var(--bg);border-color:var(--text)}.btn-primary:hover{color:var(--bg)}.btn-danger:hover{border-color:var(--status-error);color:var(--status-error)}.btn-small{padding:2px var(--space-2);font-size:var(--fs-small)}.chip{padding:1px var(--space-2);border:var(--hairline);border-radius:var(--radius);font-size:var(--fs-small);white-space:nowrap;align-items:center;gap:6px;line-height:1.5;display:inline-flex}.chip .dot{font-size:10px}.chip-running{border-color:var(--status-running);color:var(--status-running)}.chip-attention{background:var(--status-attention);border-color:var(--status-attention);color:var(--bg);font-weight:600}.chip-idle{border-color:var(--border);color:var(--status-idle)}.chip-error{border-color:var(--status-error);color:var(--status-error)}.chip-plain{border-color:var(--border);color:var(--muted)}.card{border:var(--hairline);border-radius:var(--radius);padding:var(--space-4);transition:border-color .2s}a.card{text-decoration:none;display:block}.card:hover{border-color:var(--text);color:var(--text)}.input,.textarea,.select{width:100%;padding:var(--space-2) var(--space-3);border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--text);font-size:var(--fs-body);transition:border-color .2s}.input:hover,.textarea:hover,.select:hover{border-color:var(--text)}.textarea{resize:vertical;min-height:72px;line-height:1.6}.field{gap:var(--space-1);margin-bottom:var(--space-4);flex-direction:column;display:flex}.field>label{font-size:var(--fs-secondary)}.field>.hint{font-size:var(--fs-small);color:var(--muted)}.toggle{border:var(--hairline);border-radius:var(--radius);display:inline-flex;overflow:hidden}.toggle span{padding:2px var(--space-3);font-size:var(--fs-small);color:var(--muted)}.toggle .on{background:var(--text);color:var(--bg)}.table{border-collapse:collapse;width:100%;font-size:var(--fs-body)}.table th{text-align:left;font-weight:500;font-size:var(--fs-small);color:var(--muted);padding:var(--space-2) var(--space-3);border-bottom:var(--hairline)}.table td{padding:var(--space-2) var(--space-3);border-bottom:var(--hairline)}.modal-backdrop{padding:var(--space-4);background:#80808040;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.modal{background:var(--bg);border:1px solid var(--text);border-radius:var(--radius);width:100%;max-width:480px;padding:var(--space-5)}.modal h3{margin-bottom:var(--space-3)}.modal-actions{gap:var(--space-2);margin-top:var(--space-5);justify-content:flex-end;display:flex}.row{align-items:center;gap:var(--space-2);display:flex}.stack{gap:var(--space-2);flex-direction:column;display:flex}.hidden{display:none}:root{--syntax-comment:green;--syntax-keyword:#00f;--syntax-string:#a31515;--syntax-number:#098658;--syntax-function:#795e26;--syntax-type:#267f99;--syntax-variable:#001080;--syntax-operator:#000;--syntax-punctuation:#000}@media (prefers-color-scheme:dark){:root{--syntax-comment:#6a9955;--syntax-keyword:#569cd6;--syntax-string:#ce9178;--syntax-number:#b5cea8;--syntax-function:#dcdcaa;--syntax-type:#4ec9b0;--syntax-variable:#9cdcfe;--syntax-operator:#d4d4d4;--syntax-punctuation:#d4d4d4}}html,body,#root{width:100%;min-width:0;max-width:100%}#root{flex-direction:column;min-height:100vh;display:flex}.screen-fill{flex-direction:column;flex:1;min-height:0;display:flex}.fleet-head{justify-content:space-between;align-items:flex-start;gap:var(--space-4);margin:var(--space-5) 0 var(--space-6);flex-wrap:wrap;display:flex}.live-sessions{margin-bottom:var(--space-6)}.past-sessions-head{font-size:var(--fs-h3);color:var(--muted);border-bottom:var(--hairline);padding-bottom:var(--space-2);margin-bottom:var(--space-4)}.project-group{margin-bottom:var(--space-5)}.group-head{align-items:baseline;gap:var(--space-3);padding-bottom:var(--space-1);margin-bottom:var(--space-1);flex-wrap:wrap;display:flex}.group-head h2,.group-head h3{font-size:var(--fs-secondary);font-weight:600}.group-head .btn{margin-left:auto}.session-grid{gap:var(--space-4);margin-bottom:var(--space-4);grid-template-columns:repeat(auto-fill,minmax(340px,1fr));display:grid}.session-card{border:var(--hairline);border-radius:var(--radius);min-width:0;padding:var(--space-4);gap:var(--space-2);flex-direction:column;transition:border-color .2s;display:flex}.session-card:hover{border-color:var(--text)}.session-card.attention{border-color:var(--status-attention)}.session-card.error{border-color:var(--status-error)}.session-title{justify-content:space-between;align-items:baseline;gap:var(--space-2);min-width:0;display:flex}.session-title .name{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-weight:500;overflow:hidden}.session-project{font-size:var(--fs-small);color:var(--muted);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.attention-reason{font-weight:600;font-size:var(--fs-secondary)}.error-reason{color:var(--status-error);font-size:var(--fs-secondary)}.activity{font-size:var(--fs-secondary);color:var(--muted);-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.subagents{border-left:var(--hairline);padding-left:var(--space-3);font-size:var(--fs-small);flex-direction:column;gap:2px;display:flex}.subagents .agent-line{color:var(--muted);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.subagents .agent-line .live{color:var(--status-running)}.session-meta{font-size:var(--fs-small);color:var(--muted);gap:var(--space-1);flex-wrap:wrap;display:flex}.session-actions{gap:var(--space-2);padding-top:var(--space-2);flex-wrap:wrap;margin-top:auto;display:flex}.disk-row{align-items:center;gap:var(--space-3);border-bottom:var(--hairline);font-size:var(--fs-secondary);padding:2px 0;display:flex}.disk-row .name{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.disk-row .meta{color:var(--muted);font-size:var(--fs-small);white-space:nowrap;margin-left:auto}.disk-row .actions{gap:var(--space-2);flex-shrink:0;display:flex}.disk-more{margin-top:var(--space-1);font-size:var(--fs-small);color:var(--muted);cursor:pointer;background:0 0;border:none;padding:0;font-family:inherit;text-decoration:underline;display:inline-block}.disk-more:hover{color:var(--text)}.empty-state{border:var(--hairline);border-radius:var(--radius);padding:var(--space-6);text-align:center;color:var(--muted);margin-top:var(--space-6)}.session-screen{flex-direction:column;width:100%;min-width:0;max-width:100%;height:100dvh;display:flex;overflow:hidden}.session-bar{border-bottom:var(--hairline);padding-top:env(safe-area-inset-top,0px);flex-shrink:0}.session-bar-inner{width:100%;min-width:0;max-width:960px;padding:var(--space-2) var(--space-4);align-items:center;gap:var(--space-3);flex-wrap:wrap;margin:0 auto;display:flex}.session-bar .back{white-space:nowrap;text-decoration:none}.session-bar .title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-weight:500;overflow:hidden}.session-bar .project,.session-bar .agent-type{color:var(--muted);font-size:var(--fs-small);white-space:nowrap}.session-bar .right{align-items:center;gap:var(--space-2);flex-wrap:wrap;min-width:0;margin-left:auto;display:flex}.session-info-bar{font-size:var(--fs-small);color:var(--muted);justify-content:space-between;padding-top:0}.session-info-left{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.session-info-right{gap:var(--space-2);flex-wrap:wrap;justify-content:flex-end;margin-left:auto;display:flex}.stats-trigger{color:var(--muted);font:inherit;cursor:pointer;background:0 0;border:none;padding:0}.stats-popover{right:max(var(--space-4), calc((100vw - 960px) / 2 + var(--space-4)));z-index:10;border:var(--hairline);border-radius:var(--radius);background:var(--bg);padding:var(--space-3);min-width:280px;position:absolute;top:78px;box-shadow:0 12px 32px #0000002e}.stats-grid{gap:var(--space-1) var(--space-3);font-size:var(--fs-small);grid-template-columns:1fr auto;display:grid}.stats-grid span{color:var(--muted)}.session-info-right span+span:before{content:"·";margin-right:var(--space-2);color:var(--muted)}.switcher{min-width:0;padding:1px var(--space-2);border:var(--hairline);border-radius:var(--radius);font-size:var(--fs-small);background:var(--bg);color:var(--text);cursor:pointer;white-space:nowrap;align-items:center;gap:6px;font-family:inherit;transition:border-color .2s;display:inline-flex}.switcher:hover{border-color:var(--text)}.switcher .label{color:var(--muted)}.switcher .value{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.model-switcher{max-width:min(340px,45vw)}.session-bar.collapsed{background:var(--bg)}.session-bar-main .chrome-toggle{margin-left:auto}.chrome-toggle{border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--muted);font:inherit;font-size:var(--fs-small);padding:1px var(--space-2);cursor:pointer;white-space:nowrap}.chrome-toggle:hover{border-color:var(--text);color:var(--text)}.chat{flex:1;min-height:0;overflow-y:auto}.chat-inner{max-width:960px;padding:var(--space-4);gap:var(--space-4);flex-direction:column;margin:0 auto;display:flex}.assistant-turn{border-left:var(--hairline);padding-left:var(--space-3);gap:var(--space-3);flex-direction:column;display:flex}.entry{min-width:0}.entry-head{font-size:var(--fs-small);color:var(--muted);margin-bottom:var(--space-1);gap:var(--space-2);align-items:baseline;display:flex}.entry-action{color:var(--muted);font:inherit;font-size:var(--fs-small);cursor:pointer;opacity:.65;background:0 0;border:none;padding:0}.entry-action:hover{opacity:1;text-decoration:underline}.skill-badge,.queued-kind{border:var(--hairline);border-radius:var(--radius);padding:0 var(--space-1);font-size:var(--fs-small);color:var(--muted)}.entry.user{align-self:flex-end;max-width:85%}.entry.user .entry-body{border:1px solid var(--text);border-radius:var(--radius);padding:var(--space-2) var(--space-3);white-space:pre-wrap}.entry.user .entry-head{justify-content:flex-end}.entry.assistant{max-width:100%}.entry.assistant .entry-body{white-space:normal}.markdown-body{color:var(--text)}.markdown-body,.markdown-body *{-webkit-text-fill-color:currentColor}.markdown-body p,.markdown-body ul,.markdown-body ol,.markdown-body pre,.markdown-body blockquote,.markdown-body table{margin:0 0 var(--space-2)}.markdown-body :last-child{margin-bottom:0}.markdown-body ul,.markdown-body ol{padding-left:var(--space-5)}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4{font-size:var(--fs-h3);margin:var(--space-2) 0 var(--space-1);font-weight:500}.markdown-body code{border:var(--hairline);border-radius:var(--radius);font-size:var(--fs-secondary);padding:0 4px}.markdown-body pre{background:var(--bg);margin-top:var(--space-1)}.markdown-body pre code{border:none;padding:0}.markdown-body a{text-decoration:underline}.markdown-body table{border-collapse:collapse;width:100%;font-size:var(--fs-secondary)}.markdown-body th,.markdown-body td{border:var(--hairline);padding:var(--space-1) var(--space-2);text-align:left}.markdown-body blockquote{border-left:var(--hairline);padding-left:var(--space-3);color:var(--muted)}.entry-body code{border:var(--hairline);border-radius:var(--radius);font-size:var(--fs-secondary);padding:0 4px}details.thinking{border-left:var(--hairline);padding-left:var(--space-3);margin-bottom:var(--space-3)}details.thinking summary{cursor:pointer;font-size:var(--fs-small);color:var(--muted);-webkit-user-select:none;user-select:none;list-style:none}details.thinking summary:before{content:"▸ "}details.thinking[open] summary:before{content:"▾ "}details.thinking .thinking-body{color:var(--muted);font-size:var(--fs-secondary);padding-top:var(--space-1);white-space:pre-wrap}.tool{border:var(--hairline);border-radius:var(--radius);font-size:var(--fs-secondary)}.tool summary{cursor:pointer;-webkit-user-select:none;user-select:none;gap:var(--space-2);padding:var(--space-1) var(--space-3);align-items:baseline;list-style:none;display:flex}.tool summary:before{content:"▸";color:var(--muted)}.tool[open] summary:before{content:"▾"}.tool .tool-name{font-weight:600}.tool .tool-arg{color:var(--muted);text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.tool .tool-status{white-space:nowrap;font-size:var(--fs-small);color:var(--muted);margin-left:auto}.tool .tool-status.running{color:var(--status-running)}.tool .tool-status.error{color:var(--status-error)}.tool .tool-command{border-top:var(--hairline);padding:var(--space-2) var(--space-3) 0;overflow-x:auto}.tool .tool-command code{border:var(--hairline);border-radius:var(--radius);padding:var(--space-1) var(--space-2);white-space:pre-wrap;display:block}.tool .tool-input{border-top:var(--hairline);padding:var(--space-2) var(--space-3);overflow-x:auto}.tool .tool-input-label{font-size:var(--fs-small);color:var(--muted);margin-bottom:var(--space-1);display:block}.tool .tool-input pre{font-size:var(--fs-small);white-space:pre-wrap;border:none;padding:0}.tool .suggested-command{margin-top:var(--space-1)}.tool .suggested-command code{border:var(--hairline);border-radius:var(--radius);padding:1px var(--space-2)}.tool .tool-result{border-top:var(--hairline);padding:var(--space-2) var(--space-3);overflow-x:auto}.tool-output-truncated,.transcript-window-control{color:var(--muted);font-size:var(--fs-small);margin-bottom:var(--space-2)}.transcript-window-control{text-align:center}.tool .tool-command+.tool-result,.tool .tool-input+.tool-result{border-top:none}.tool .tool-result pre{font-size:var(--fs-small);border:none;padding:0}.tool .tool-result pre,.tool .tool-input pre{max-height:340px;overflow-y:auto}.hljs{color:var(--text);background:0 0}.hljs-comment,.hljs-quote{color:var(--syntax-comment)}.hljs-keyword,.hljs-selector-tag{color:var(--syntax-keyword)}.hljs-number,.hljs-literal{color:var(--syntax-number)}.hljs-string,.hljs-doctag{color:var(--syntax-string)}.hljs-function,.hljs-title,.hljs-title.function_,.hljs-section,.hljs-name{color:var(--syntax-function)}.hljs-type,.hljs-class,.hljs-title.class_,.hljs-built_in{color:var(--syntax-type)}.hljs-attr,.hljs-variable,.hljs-variable.language_,.hljs-params,.hljs-property{color:var(--syntax-variable)}.hljs-meta,.hljs-meta .hljs-keyword,.hljs-meta .hljs-string{color:var(--syntax-keyword)}.hljs-operator{color:var(--syntax-operator)}.hljs-punctuation{color:var(--syntax-punctuation)}.hljs-subst{color:var(--text)}.diff-add{color:var(--status-running)}.diff-del{color:var(--status-error)}.entry.summary-card .entry-body{border:var(--hairline);border-radius:var(--radius);padding:var(--space-2) var(--space-3);color:var(--muted);font-size:var(--fs-secondary)}.entry.summary-card summary{cursor:pointer;-webkit-user-select:none;user-select:none;color:var(--muted);font-size:var(--fs-secondary);list-style:none}.entry.summary-card summary:before{content:"▸ "}.entry.summary-card details[open] summary:before{content:"▾ "}.entry.custom-card .entry-body,.entry.agent-result-card details{border:var(--hairline);border-radius:var(--radius);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary)}.entry.agent-result-card summary{cursor:pointer;-webkit-user-select:none;user-select:none;gap:var(--space-2);color:var(--muted);align-items:baseline;list-style:none;display:flex}.entry.agent-result-card summary:before{content:"▸ "}.entry.agent-result-card details[open] summary:before{content:"▾ "}.agent-result-header{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.entry.agent-result-card .entry-body{margin-top:var(--space-2)}.streaming-cursor:after{content:"▌";color:var(--status-running)}.dock{border-top:var(--hairline);flex-shrink:0}.dock.collapsed{background:var(--bg)}.dock-collapse-row{max-width:960px;padding:var(--space-1) calc(var(--space-4) + env(safe-area-inset-right,0px)) calc(var(--space-1) + env(safe-area-inset-bottom,0px)) calc(var(--space-4) + env(safe-area-inset-left,0px));align-items:center;gap:var(--space-3);font-size:var(--fs-small);color:var(--muted);margin:0 auto;display:flex}.dock-collapsed-hint{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.dock-inner{max-width:960px;padding:var(--space-3) calc(var(--space-4) + env(safe-area-inset-right,0px)) calc(var(--space-3) + env(safe-area-inset-bottom,0px)) calc(var(--space-4) + env(safe-area-inset-left,0px));gap:var(--space-2);flex-direction:column;margin:0 auto;display:flex}.tasks{border:var(--hairline);border-radius:var(--radius);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary)}.tasks summary{cursor:pointer;-webkit-user-select:none;user-select:none;color:var(--muted);font-size:var(--fs-small);list-style:none}.tasks summary:before{content:"▸ "}.tasks[open] summary:before{content:"▾ "}.tasks ul{padding-top:var(--space-1);list-style:none}.tasks .done{color:var(--muted)}.tasks .active{font-weight:600}.subagent-strip{border:var(--hairline);border-radius:var(--radius);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary);align-items:center;gap:var(--space-3);flex-wrap:wrap;display:flex}.subagent-strip .count,.subagent-strip .collapsed-hint{color:var(--muted);font-size:var(--fs-small);white-space:nowrap}.subagent-strip .subagent-toggle{border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--text);font-family:inherit;font-size:var(--fs-small);padding:1px var(--space-2);cursor:pointer}.subagent-strip .subagent-toggle:hover{border-color:var(--text)}.subagent-strip.collapsed{padding-block:var(--space-1)}.subagent-strip .agent-chip{border:var(--hairline);border-radius:var(--radius);padding:1px var(--space-2);font-size:var(--fs-small);background:var(--bg);color:var(--text);cursor:pointer;align-items:center;gap:6px;max-width:260px;font-family:inherit;transition:border-color .2s;display:inline-flex}.subagent-strip .agent-chip:hover{border-color:var(--text)}.subagent-strip .agent-chip .live{color:var(--status-running)}.subagent-strip .agent-chip .done{color:var(--muted)}.subagent-strip .agent-chip .task{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.status-line{align-items:center;gap:var(--space-3);font-size:var(--fs-secondary);flex-wrap:wrap;display:flex}.status-line .working{color:var(--status-running)}.status-line .warning{color:var(--status-attention)}.status-line .error{color:var(--status-error)}.status-line .queued{color:var(--muted);font-size:var(--fs-small)}.status-line .btn{margin-left:auto}.status-line .inline-stop{margin-left:var(--space-2)}.composer{gap:var(--space-2);flex-direction:column;display:flex;position:relative}.queued-message-row{border:var(--hairline);border-radius:var(--radius);padding:var(--space-2);align-items:center;gap:var(--space-2);font-size:var(--fs-small);flex-wrap:wrap;display:flex}.queued-chip{align-items:baseline;gap:var(--space-1);text-overflow:ellipsis;white-space:nowrap;max-width:240px;color:var(--muted);display:inline-flex;overflow:hidden}.attachment-strip{gap:var(--space-2);flex-wrap:wrap;display:flex}.attachment-thumb,.attachment-file{border:var(--hairline);border-radius:var(--radius);padding:2px;position:relative}.attachment-file{align-items:baseline;gap:var(--space-2);padding:2px var(--space-2);font-size:var(--fs-small);overflow-wrap:anywhere;max-width:100%;display:inline-flex}.attachment-thumb img{object-fit:cover;width:72px;height:54px;display:block}.attachment-thumb button,.attachment-file button{border:var(--hairline);background:var(--bg);color:var(--text);cursor:pointer;border-radius:999px;position:absolute;top:-8px;right:-8px}.command-popover{left:0;right:0;bottom:calc(100% + var(--space-2));border:var(--hairline);border-radius:var(--radius);background:var(--bg);padding:var(--space-1);z-index:5;max-height:260px;position:absolute;overflow-y:auto}.command-option{background:var(--bg);width:100%;color:var(--text);font:inherit;text-align:left;padding:var(--space-2);border-radius:var(--radius);gap:var(--space-2);cursor:pointer;border:none;grid-template-columns:minmax(120px,max-content) 1fr max-content;align-items:baseline;display:grid}.command-option:hover,.command-option.selected{background:var(--text);color:var(--bg)}.command-name{font-weight:500}.command-description{color:var(--muted);font-size:var(--fs-small);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.command-source{border:var(--hairline);border-radius:var(--radius);color:var(--muted);font-size:var(--fs-small);padding:0 var(--space-1)}.command-option.selected .command-description,.command-option:hover .command-description,.command-option.selected .command-source,.command-option:hover .command-source{color:var(--bg)}.composer textarea{border:var(--hairline);border-radius:var(--radius);background:var(--bg);width:100%;color:var(--text);padding:var(--space-2) var(--space-3);font-size:var(--fs-body);resize:vertical;min-height:60px;max-height:40vh;font-family:inherit;line-height:1.6;transition:border-color .2s;overflow-y:hidden}.composer textarea:hover,.composer textarea:focus{border-color:var(--text);outline:none}.hidden-file-input{display:none}.composer-row{align-items:center;gap:var(--space-2);flex-wrap:wrap;display:flex}.mode-toggle{border:var(--hairline);border-radius:var(--radius);font-size:var(--fs-small);display:inline-flex;overflow:hidden}.mode-toggle button{background:var(--bg);color:var(--muted);padding:2px var(--space-3);cursor:pointer;border:none;font-family:inherit}.mode-toggle button.selected{background:var(--text);color:var(--bg)}.composer-row .send{margin-left:auto}.ghost-suggest{font-size:var(--fs-small);color:var(--muted);cursor:pointer;background:0 0;border:none;font-family:inherit}.ghost-suggest .key{border:var(--hairline);border-radius:var(--radius);padding:0 4px}.readonly-note{border:var(--hairline);border-radius:var(--radius);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary);color:var(--muted)}.fallback-banner{border:1px solid var(--status-attention);border-radius:var(--radius);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary);gap:var(--space-3);align-items:baseline;display:flex}.fallback-banner .dismiss{margin-left:auto}.widget-block{border:var(--hairline);border-radius:var(--radius);padding:var(--space-2) var(--space-3);font-size:var(--fs-small);color:var(--muted);white-space:pre-wrap}.toast-region{bottom:var(--space-4);right:var(--space-4);gap:var(--space-2);z-index:50;flex-direction:column;max-width:360px;display:flex;position:fixed}.toast{border:var(--hairline);border-radius:var(--radius);background:var(--bg);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary);gap:var(--space-2);align-items:baseline;display:flex}.toast.warning{border-color:var(--status-attention)}.toast.error{border-color:var(--status-error)}.files-head{justify-content:space-between;align-items:flex-start;gap:var(--space-4);margin:var(--space-5) 0 var(--space-4);flex-wrap:wrap;display:flex}.crumbs{font-size:var(--fs-secondary);flex-wrap:wrap;align-items:baseline;gap:2px;display:flex}.crumbs .sep{color:var(--muted);margin:0 2px}.scope-note{color:var(--muted);font-size:var(--fs-small);margin-top:var(--space-1)}.head-actions{gap:var(--space-2);flex-wrap:wrap;display:flex}.places{gap:var(--space-2);margin-bottom:var(--space-4);flex-wrap:wrap;align-items:baseline;display:flex}.places .label{color:var(--muted);font-size:var(--fs-small)}.place-chip{border:var(--hairline);border-radius:var(--radius);padding:1px var(--space-2);font-size:var(--fs-small);background:var(--bg);color:var(--text);cursor:pointer;font-family:inherit;text-decoration:none;transition:border-color .2s}.place-chip:hover{border-color:var(--text)}.file-table{border-collapse:collapse;width:100%;font-size:var(--fs-secondary)}.file-table th{text-align:left;font-size:var(--fs-small);color:var(--muted);border-bottom:var(--hairline);padding:var(--space-1) var(--space-2);font-weight:400}.file-table td{border-bottom:var(--hairline);padding:var(--space-2)}.file-table .name{color:var(--text);cursor:pointer;font-family:inherit;font-size:inherit;background:0 0;border:none;padding:0;text-decoration:none}.file-table .name .icon{color:var(--muted);margin-right:var(--space-1)}.file-table .meta{color:var(--muted);font-size:var(--fs-small);white-space:nowrap}.file-table .actions{text-align:right}.drop-zone{border:1px dashed var(--border);border-radius:var(--radius);padding:var(--space-4);text-align:center;color:var(--muted);font-size:var(--fs-small);margin-top:var(--space-4);transition:border-color .2s}.drop-zone.active{border-color:var(--text);color:var(--text)}.upload-warning{font-size:var(--fs-small);color:var(--muted);margin-top:var(--space-2)}.settings-wrap{max-width:720px}.settings-intro{color:var(--muted);font-size:var(--fs-secondary);margin:var(--space-2) 0 var(--space-5)}.settings-section{margin-bottom:var(--space-6)}.settings-section h2{font-size:var(--fs-h3);border-bottom:var(--hairline);padding-bottom:var(--space-2);margin-bottom:var(--space-3)}.setting-row{justify-content:space-between;align-items:center;gap:var(--space-4);padding:var(--space-2) 0;display:flex}.setting-label{flex-direction:column;gap:2px;min-width:0;display:flex}.setting-label .name{font-size:var(--fs-secondary)}.setting-label .hint{font-size:var(--fs-small);color:var(--muted)}.setting-control select,.setting-control input{border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--text);font-family:inherit;font-size:var(--fs-secondary);padding:2px var(--space-2)}.checkbox-control{align-items:center;gap:var(--space-2);font-size:var(--fs-secondary);display:inline-flex}.checkbox-control input{width:auto}.settings-error{border:1px solid var(--status-error);border-radius:var(--radius);color:var(--status-error);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary);margin-bottom:var(--space-4);white-space:pre-wrap}.settings-warning{border:1px solid var(--status-attention);border-radius:var(--radius);color:var(--status-attention);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary);margin-bottom:var(--space-4);white-space:pre-wrap}.model-picker-button{white-space:normal;overflow-wrap:anywhere;text-align:left;min-width:0;max-width:min(100%,520px)}.agent-model-row{gap:var(--space-3);padding:var(--space-2) 0;border-bottom:var(--hairline);grid-template-columns:minmax(0,1fr) minmax(0,1.2fr) auto;align-items:start;display:grid}.agent-model-summary{flex-direction:column;gap:2px;min-width:0;display:flex}.agent-model-name{font-size:var(--fs-secondary);font-weight:600}.agent-model-description{color:var(--muted);font-size:var(--fs-small)}.agent-model-fallbacks{gap:var(--space-1);flex-wrap:wrap;min-width:0;display:flex}.agent-model-chip{border:var(--hairline);border-radius:var(--radius);color:var(--muted);font-size:var(--fs-small);padding:0 var(--space-1);overflow-wrap:anywhere;white-space:normal;min-width:0;max-width:100%}.agent-model-editor{gap:var(--space-2);border:var(--hairline);border-radius:var(--radius);padding:var(--space-2);flex-direction:column;grid-column:1/-1;display:flex}.agent-model-entry{justify-content:space-between;gap:var(--space-2);font-size:var(--fs-secondary);align-items:center;min-width:0;display:flex}.agent-model-entry>span:first-child{overflow-wrap:anywhere;min-width:0}.agent-model-entry-actions{gap:var(--space-1);flex-shrink:0;display:flex}.settings-footnote{margin-top:calc(-1 * var(--space-3));margin-bottom:var(--space-5)}.device-row{align-items:center;gap:var(--space-3);padding:var(--space-2) 0;border-bottom:var(--hairline);font-size:var(--fs-secondary);display:flex}.device-row .meta{color:var(--muted);font-size:var(--fs-small);margin-left:auto}.this-device{border:var(--hairline);border-radius:var(--radius);font-size:var(--fs-small);color:var(--muted);padding:0 var(--space-1);margin-left:var(--space-1)}.pair-wrap{min-height:100vh;padding:var(--space-4);justify-content:center;align-items:center;display:flex}.pair-card{border:var(--hairline);border-radius:var(--radius);width:100%;max-width:460px;padding:var(--space-6);gap:var(--space-4);flex-direction:column;display:flex}.pair-card h1{font-size:var(--fs-h2)}.pair-card .sub{color:var(--muted);font-size:var(--fs-secondary)}.identity{gap:var(--space-2);border:var(--hairline);border-radius:var(--radius);padding:var(--space-3);font-size:var(--fs-secondary);flex-direction:column;display:flex}.identity .row-line{justify-content:space-between;gap:var(--space-3);display:flex}.identity .k{color:var(--muted);font-size:var(--fs-small)}.pin-input{text-align:center;width:100%;font-size:var(--fs-h1);letter-spacing:.5em;border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--text);padding:var(--space-2);font-family:inherit}.pin-input:focus{border-color:var(--text);outline:none}.pin-hint{font-size:var(--fs-small);color:var(--muted)}.security-note{border-top:var(--hairline);padding-top:var(--space-3);gap:var(--space-2);font-size:var(--fs-small);color:var(--muted);flex-direction:column;display:flex}.pair-error{color:var(--status-error);font-size:var(--fs-secondary)}.denied-card{border:1px solid var(--status-error);border-radius:var(--radius);width:100%;max-width:460px;padding:var(--space-6);gap:var(--space-3);flex-direction:column;display:flex}.field{gap:var(--space-1);flex-direction:column;display:flex}.field label{font-size:var(--fs-small);color:var(--muted)}.field input,.field textarea,.field select{border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--text);font-family:inherit;font-size:var(--fs-secondary);padding:var(--space-2)}.field input:focus,.field textarea:focus{border-color:var(--text);outline:none}.recent-projects{flex-direction:column;gap:2px;max-height:160px;display:flex;overflow-y:auto}.recent-projects button{text-align:left;color:var(--text);font-family:inherit;font-size:var(--fs-secondary);padding:var(--space-1) var(--space-2);cursor:pointer;border-radius:var(--radius);background:0 0;border:none}.recent-projects button:hover{background:var(--border)}.model-scope-tabs{border:var(--hairline);border-radius:var(--radius);margin-bottom:var(--space-3);font-size:var(--fs-small);display:inline-flex;overflow:hidden}.model-scope-tabs button{background:var(--bg);color:var(--muted);padding:2px var(--space-3);cursor:pointer;border:none;font-family:inherit}.model-scope-tabs button.selected{background:var(--text);color:var(--bg)}.model-picker-modal{min-width:min(360px,100vw - 32px);max-width:min(900px,100vw - 32px);overflow:auto}@media (width>=701px){.model-picker-modal{resize:horizontal}}.model-list{gap:var(--space-2);flex-direction:column;min-width:0;display:flex;overflow:hidden auto}.model-provider-group{flex-direction:column;gap:2px;display:flex}.model-provider-heading{background:var(--bg);color:var(--muted);font-size:var(--fs-small);border-bottom:var(--hairline);padding:var(--space-1) var(--space-2);z-index:1;position:sticky;top:0}.model-row{align-items:start;gap:2px var(--space-2);text-align:left;border:var(--hairline);border-radius:var(--radius);color:var(--text);font-family:inherit;font-size:var(--fs-secondary);padding:var(--space-1) var(--space-2);cursor:pointer;background:0 0;grid-template-columns:18px minmax(0,1fr) auto auto;min-width:0;display:grid}.model-row:hover,.model-row.current{border-color:var(--text)}.model-current{color:var(--status-running);grid-area:1/1}.model-id{overflow-wrap:anywhere;white-space:normal;grid-area:1/2;min-width:0;font-weight:600}.model-name{color:var(--muted);font-size:var(--fs-small);overflow-wrap:anywhere;white-space:normal;grid-area:2/2/auto/-1;min-width:0}.model-provider-badge,.model-reasoning{color:var(--muted);font-size:var(--fs-small);white-space:nowrap;border:var(--hairline);border-radius:var(--radius);padding:0 var(--space-1);grid-row:1}.model-provider-badge{grid-column:3}.model-reasoning{grid-column:4}.context-modal-body{gap:var(--space-4);flex-direction:column;display:flex}.context-section h3{font-size:var(--fs-secondary);margin-bottom:var(--space-1)}.context-section ul{gap:var(--space-1);font-size:var(--fs-small);flex-direction:column;list-style:none;display:flex}.context-section li{overflow-wrap:anywhere}.fork-message-list{gap:var(--space-2);flex-direction:column;max-height:360px;display:flex;overflow-y:auto}.fork-message{border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--text);font:inherit;text-align:left;padding:var(--space-2);cursor:pointer;gap:var(--space-2);display:flex}.fork-message:hover{border-color:var(--text)}.fork-entry-id{color:var(--muted);font-size:var(--fs-small);flex:none}@media (width<=700px){.container,.topbar-inner,.session-bar-inner,.dock-inner,.dock-collapse-row,.chat-inner{width:100%;min-width:0;max-width:100%;padding-left:var(--space-3);padding-right:var(--space-3)}.session-grid{grid-template-columns:minmax(0,1fr)}.session-card,.disk-row,.session-meta,.session-actions,.disk-row .actions{flex-wrap:wrap;min-width:0;max-width:100%}.disk-row .meta{order:3;width:100%;margin-left:0}.session-bar .project{display:none}.session-bar .right{align-items:stretch;width:100%;margin-left:0}.session-bar .right .switcher{justify-content:center}.session-bar .right .model-switcher{flex:100%;max-width:100%}.entry.user{max-width:100%}.file-table .cols{display:none}.setting-row{flex-direction:column;align-items:stretch}.setting-control{align-self:flex-end}.agent-model-row{grid-template-columns:1fr}.agent-model-edit{justify-self:start}.agent-model-entry{flex-direction:column;align-items:flex-start}.subagent-strip{align-items:stretch;gap:var(--space-2)}.subagent-strip .subagent-toggle{flex:none;min-height:32px}.subagent-strip .count,.subagent-strip .collapsed-hint{white-space:normal}.subagent-strip .agent-chip{max-width:100%}.head-actions .btn{flex:1}.composer textarea{font-size:16px}}
|
package/dist/static/index.html
CHANGED
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,600;1,400&display=swap"
|
|
24
24
|
rel="stylesheet"
|
|
25
25
|
/>
|
|
26
|
-
<script type="module" crossorigin src="./assets/index-
|
|
27
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
26
|
+
<script type="module" crossorigin src="./assets/index-BfEMioIL.js"></script>
|
|
27
|
+
<link rel="stylesheet" crossorigin href="./assets/index-DN6kUWdQ.css">
|
|
28
28
|
</head>
|
|
29
29
|
<body>
|
|
30
30
|
<div id="root"></div>
|
package/dist/static/sw.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
// Replaced at build time; see vite.config.ts generateBundle hook.
|
|
17
|
-
const SW_CACHE_VERSION = "2.
|
|
17
|
+
const SW_CACHE_VERSION = "2.37.0";
|
|
18
18
|
const SHELL_CACHE = `dreb-dashboard-shell-v${SW_CACHE_VERSION}`;
|
|
19
19
|
|
|
20
20
|
// Shell assets (HTML, JS, CSS) get a network-first strategy. The dashboard
|
|
@@ -26,25 +26,54 @@ const SHELL_PRECACHE = [
|
|
|
26
26
|
];
|
|
27
27
|
|
|
28
28
|
self.addEventListener("install", (event) => {
|
|
29
|
+
// Pre-cache entries individually so a single failed URL does not reject
|
|
30
|
+
// the whole install (which would prevent skipWaiting/activation and leave
|
|
31
|
+
// notifications + installability permanently unavailable with no error).
|
|
32
|
+
// Degraded cache is acceptable; activation must proceed unconditionally —
|
|
33
|
+
// so a CacheStorage failure at the top level (e.g. `caches.open` rejecting
|
|
34
|
+
// under private browsing / quota exhaustion / storage corruption) is caught
|
|
35
|
+
// here and skipWaiting still runs, keeping notifications + installability
|
|
36
|
+
// available even when the precache store is unusable.
|
|
29
37
|
event.waitUntil(
|
|
30
38
|
caches
|
|
31
39
|
.open(SHELL_CACHE)
|
|
32
|
-
.then((cache) =>
|
|
33
|
-
|
|
40
|
+
.then((cache) =>
|
|
41
|
+
Promise.all(
|
|
42
|
+
SHELL_PRECACHE.map((url) =>
|
|
43
|
+
cache.add(url).catch((err) => {
|
|
44
|
+
console.warn(`[sw] precache failed for ${url}:`, err);
|
|
45
|
+
}),
|
|
46
|
+
),
|
|
47
|
+
),
|
|
48
|
+
)
|
|
49
|
+
.then(() => self.skipWaiting())
|
|
50
|
+
.catch((err) => {
|
|
51
|
+
console.error("[sw] install cache open failed, forcing activation:", err);
|
|
52
|
+
return self.skipWaiting();
|
|
53
|
+
}),
|
|
34
54
|
);
|
|
35
55
|
});
|
|
36
56
|
|
|
37
57
|
self.addEventListener("activate", (event) => {
|
|
38
58
|
event.waitUntil(
|
|
39
59
|
Promise.all([
|
|
40
|
-
// Drop caches from prior versions.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
)
|
|
47
|
-
|
|
60
|
+
// Drop caches from prior versions. Best-effort: a CacheStorage failure
|
|
61
|
+
// here (e.g. private browsing) must not reject the activate event and
|
|
62
|
+
// prevent clients.claim() from running — otherwise already-open tabs
|
|
63
|
+
// stay controlled by the stale SW and never receive notifications from
|
|
64
|
+
// the new version until a full reload.
|
|
65
|
+
caches
|
|
66
|
+
.keys()
|
|
67
|
+
.then((keys) =>
|
|
68
|
+
Promise.all(
|
|
69
|
+
keys
|
|
70
|
+
.filter((key) => key.startsWith("dreb-dashboard-") && key !== SHELL_CACHE)
|
|
71
|
+
.map((key) => caches.delete(key)),
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
.catch((err) => {
|
|
75
|
+
console.error("[sw] cache cleanup failed:", err);
|
|
76
|
+
}),
|
|
48
77
|
self.clients.claim(),
|
|
49
78
|
]),
|
|
50
79
|
);
|
|
@@ -52,22 +81,41 @@ self.addEventListener("activate", (event) => {
|
|
|
52
81
|
|
|
53
82
|
// Network-first for navigations and same-origin GETs. On network failure only,
|
|
54
83
|
// fall back to cache (so a brief blip doesn't blank the dashboard).
|
|
84
|
+
// Clone-and-store a fresh response in the shell cache; cache failures are
|
|
85
|
+
// non-fatal (the response is still returned to the page).
|
|
86
|
+
function cachePut(req, res) {
|
|
87
|
+
const copy = res.clone();
|
|
88
|
+
caches.open(SHELL_CACHE).then((cache) => cache.put(req, copy)).catch(() => {});
|
|
89
|
+
}
|
|
90
|
+
|
|
55
91
|
self.addEventListener("fetch", (event) => {
|
|
56
92
|
const req = event.request;
|
|
57
93
|
if (req.method !== "GET") return;
|
|
58
94
|
const url = new URL(req.url);
|
|
59
95
|
if (url.origin !== self.location.origin) return; // never touch cross-origin (e.g. fonts CDN)
|
|
96
|
+
// The dashboard is a live control surface (SSE + RPC); API responses and
|
|
97
|
+
// the infinite EventSource stream must NEVER be cached (caching them hangs
|
|
98
|
+
// cache.put and serves stale data). Pass /api/* straight through to network.
|
|
99
|
+
if (url.pathname.startsWith("/api/")) return;
|
|
60
100
|
|
|
61
101
|
// Navigations: network-first, fall back to cached index.html.
|
|
62
102
|
if (req.mode === "navigate") {
|
|
63
103
|
event.respondWith(
|
|
64
104
|
fetch(req)
|
|
65
105
|
.then((res) => {
|
|
66
|
-
|
|
67
|
-
|
|
106
|
+
// Only cache a successful navigation. A transient 4xx/5xx would
|
|
107
|
+
// otherwise be stored as the offline shell and served on the next
|
|
108
|
+
// disconnected visit, showing a broken cached error page instead of
|
|
109
|
+
// the graceful 503 fallback below.
|
|
110
|
+
if (res && res.ok) cachePut(req, res);
|
|
68
111
|
return res;
|
|
69
112
|
})
|
|
70
|
-
.catch(() =>
|
|
113
|
+
.catch(() =>
|
|
114
|
+
caches
|
|
115
|
+
.match(req)
|
|
116
|
+
.then((r) => r || caches.match("./"))
|
|
117
|
+
.then((r) => r || new Response("Service unavailable", { status: 503, statusText: "Service Unavailable" })),
|
|
118
|
+
),
|
|
71
119
|
);
|
|
72
120
|
return;
|
|
73
121
|
}
|
|
@@ -76,10 +124,7 @@ self.addEventListener("fetch", (event) => {
|
|
|
76
124
|
event.respondWith(
|
|
77
125
|
fetch(req)
|
|
78
126
|
.then((res) => {
|
|
79
|
-
if (res && res.status === 200 && res.type === "basic")
|
|
80
|
-
const copy = res.clone();
|
|
81
|
-
caches.open(SHELL_CACHE).then((cache) => cache.put(req, copy)).catch(() => {});
|
|
82
|
-
}
|
|
127
|
+
if (res && res.status === 200 && res.type === "basic") cachePut(req, res);
|
|
83
128
|
return res;
|
|
84
129
|
})
|
|
85
130
|
.catch(() => caches.match(req)),
|
|
@@ -94,17 +139,35 @@ self.addEventListener("notificationclick", (event) => {
|
|
|
94
139
|
event.notification.close();
|
|
95
140
|
event.waitUntil(
|
|
96
141
|
(async () => {
|
|
97
|
-
const allClients = await self.clients.matchAll({ type: "window", includeUncontrolled: true });
|
|
98
|
-
if (allClients.length > 0) {
|
|
99
|
-
const client = allClients[0];
|
|
100
|
-
await client.focus();
|
|
101
|
-
if (sessionKey) client.postMessage({ type: "navigate-session", sessionKey });
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
// No open dashboard — open one. The client reads a pending-navigation
|
|
105
|
-
// hint from sessionStorage on load (see app.tsx).
|
|
106
142
|
const url = sessionKey ? `./#session/${encodeURIComponent(sessionKey)}` : "./";
|
|
107
|
-
|
|
143
|
+
try {
|
|
144
|
+
const allClients = await self.clients.matchAll({ type: "window", includeUncontrolled: true });
|
|
145
|
+
if (allClients.length > 0) {
|
|
146
|
+
const client = allClients[0];
|
|
147
|
+
await client.focus();
|
|
148
|
+
if (sessionKey) client.postMessage({ type: "navigate-session", sessionKey });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// No open dashboard — open one, routed to the session by the URL
|
|
152
|
+
// hash. The fresh client parses window.location.hash on load
|
|
153
|
+
// (see state/store.ts parseHash()); there is no sessionStorage
|
|
154
|
+
// handoff. The hash encodes the session so a page opened without
|
|
155
|
+
// an existing client still lands on the right route.
|
|
156
|
+
await self.clients.openWindow(url);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
// focus()/postMessage() can reject (e.g. client closed mid-focus).
|
|
159
|
+
// The notification is already closed above, so without a fallback a
|
|
160
|
+
// rejection would leave the user with no visible action.
|
|
161
|
+
console.warn("[sw] notificationclick handler failed:", err);
|
|
162
|
+
try {
|
|
163
|
+
await self.clients.openWindow(url);
|
|
164
|
+
} catch (fallbackErr) {
|
|
165
|
+
// Total failure (e.g. popup blocked in a restricted context): the
|
|
166
|
+
// notification is gone and no window opened. Surface a loud error
|
|
167
|
+
// rather than silently swallowing it — there's no other signal.
|
|
168
|
+
console.error("[sw] notificationclick openWindow fallback failed:", fallbackErr);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
108
171
|
})(),
|
|
109
172
|
);
|
|
110
173
|
});
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{--bg:#fff;--text:#000;--border:#e0e0e0;--muted:#666;--status-running:#00871f;--status-attention:#8a5400;--status-idle:#666;--status-error:#d10041;--mono-font:"IBM Plex Mono", "Courier New", monospace;--fs-small:12px;--fs-secondary:13px;--fs-body:14px;--fs-h3:16px;--fs-h2:18px;--fs-h1:24px;--space-1:4px;--space-2:8px;--space-3:12px;--space-4:16px;--space-5:24px;--space-6:32px;--space-7:48px;--radius:3px;--hairline:1px solid var(--border)}@media (prefers-color-scheme:dark){:root{--bg:#000;--text:#fff;--border:#333;--muted:#999;--status-running:#00e436;--status-attention:#ffa300;--status-idle:#999;--status-error:#ff004d}}*{box-sizing:border-box;margin:0;padding:0}html{-webkit-text-size-adjust:100%}img,svg{max-width:100%;display:block}button,input,textarea,select{font:inherit;color:inherit}body{background:var(--bg);color:var(--text);font-family:var(--mono-font);font-size:var(--fs-body);overflow-wrap:break-word;line-height:1.6;overflow-x:hidden}h1{font-size:var(--fs-h1);font-weight:500}h2{font-size:var(--fs-h2);font-weight:500}h3{font-size:var(--fs-h3);font-weight:500}a{color:var(--text);cursor:pointer}a:hover{color:var(--muted)}code,pre{font-family:var(--mono-font);font-size:var(--fs-secondary)}pre{border:var(--hairline);border-radius:var(--radius);padding:var(--space-3);white-space:pre-wrap;word-break:break-word;overflow-x:auto}::placeholder{color:var(--muted);opacity:1}:focus-visible{outline:1px solid var(--text);outline-offset:2px}.muted{color:var(--muted)}.small{font-size:var(--fs-small)}.container{max-width:1200px;padding:var(--space-4) var(--space-5);margin:0 auto}.topbar{border-bottom:var(--hairline)}.topbar-inner{max-width:1200px;padding:var(--space-3) var(--space-5);align-items:center;gap:var(--space-4);flex-wrap:wrap;margin:0 auto;display:flex}.wordmark{font-size:var(--fs-h2);letter-spacing:.04em;font-weight:600;text-decoration:none}.topbar nav{gap:var(--space-4);align-items:center;display:flex}.topbar nav a{padding:var(--space-1) 0;border-bottom:1px solid #0000;text-decoration:none}.topbar nav a:hover{color:var(--text);border-bottom-color:var(--text)}.topbar nav a[aria-current=page]{border-bottom-color:var(--text)}.topbar-spacer{margin-left:auto}footer{border-top:var(--hairline);margin-top:var(--space-7);padding:var(--space-4) 0;font-size:var(--fs-secondary);color:var(--muted)}.btn{align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-4);border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--text);cursor:pointer;font-size:var(--fs-body);text-decoration:none;transition:border-color .2s;display:inline-flex}.btn:hover{border-color:var(--text);color:var(--text)}.btn-primary{background:var(--text);color:var(--bg);border-color:var(--text)}.btn-primary:hover{color:var(--bg)}.btn-danger:hover{border-color:var(--status-error);color:var(--status-error)}.btn-small{padding:2px var(--space-2);font-size:var(--fs-small)}.chip{padding:1px var(--space-2);border:var(--hairline);border-radius:var(--radius);font-size:var(--fs-small);white-space:nowrap;align-items:center;gap:6px;line-height:1.5;display:inline-flex}.chip .dot{font-size:10px}.chip-running{border-color:var(--status-running);color:var(--status-running)}.chip-attention{background:var(--status-attention);border-color:var(--status-attention);color:var(--bg);font-weight:600}.chip-idle{border-color:var(--border);color:var(--status-idle)}.chip-error{border-color:var(--status-error);color:var(--status-error)}.chip-plain{border-color:var(--border);color:var(--muted)}.card{border:var(--hairline);border-radius:var(--radius);padding:var(--space-4);transition:border-color .2s}a.card{text-decoration:none;display:block}.card:hover{border-color:var(--text);color:var(--text)}.input,.textarea,.select{width:100%;padding:var(--space-2) var(--space-3);border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--text);font-size:var(--fs-body);transition:border-color .2s}.input:hover,.textarea:hover,.select:hover{border-color:var(--text)}.textarea{resize:vertical;min-height:72px;line-height:1.6}.field{gap:var(--space-1);margin-bottom:var(--space-4);flex-direction:column;display:flex}.field>label{font-size:var(--fs-secondary)}.field>.hint{font-size:var(--fs-small);color:var(--muted)}.toggle{border:var(--hairline);border-radius:var(--radius);display:inline-flex;overflow:hidden}.toggle span{padding:2px var(--space-3);font-size:var(--fs-small);color:var(--muted)}.toggle .on{background:var(--text);color:var(--bg)}.table{border-collapse:collapse;width:100%;font-size:var(--fs-body)}.table th{text-align:left;font-weight:500;font-size:var(--fs-small);color:var(--muted);padding:var(--space-2) var(--space-3);border-bottom:var(--hairline)}.table td{padding:var(--space-2) var(--space-3);border-bottom:var(--hairline)}.modal-backdrop{padding:var(--space-4);background:#80808040;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.modal{background:var(--bg);border:1px solid var(--text);border-radius:var(--radius);width:100%;max-width:480px;padding:var(--space-5)}.modal h3{margin-bottom:var(--space-3)}.modal-actions{gap:var(--space-2);margin-top:var(--space-5);justify-content:flex-end;display:flex}.row{align-items:center;gap:var(--space-2);display:flex}.stack{gap:var(--space-2);flex-direction:column;display:flex}.hidden{display:none}:root{--syntax-comment:green;--syntax-keyword:#00f;--syntax-string:#a31515;--syntax-number:#098658;--syntax-function:#795e26;--syntax-type:#267f99;--syntax-variable:#001080;--syntax-operator:#000;--syntax-punctuation:#000}@media (prefers-color-scheme:dark){:root{--syntax-comment:#6a9955;--syntax-keyword:#569cd6;--syntax-string:#ce9178;--syntax-number:#b5cea8;--syntax-function:#dcdcaa;--syntax-type:#4ec9b0;--syntax-variable:#9cdcfe;--syntax-operator:#d4d4d4;--syntax-punctuation:#d4d4d4}}html,body,#root{width:100%;min-width:0;max-width:100%}#root{flex-direction:column;min-height:100vh;display:flex}.screen-fill{flex-direction:column;flex:1;min-height:0;display:flex}.fleet-head{justify-content:space-between;align-items:flex-start;gap:var(--space-4);margin:var(--space-5) 0 var(--space-6);flex-wrap:wrap;display:flex}.live-sessions{margin-bottom:var(--space-6)}.past-sessions-head{font-size:var(--fs-h3);color:var(--muted);border-bottom:var(--hairline);padding-bottom:var(--space-2);margin-bottom:var(--space-4)}.project-group{margin-bottom:var(--space-5)}.group-head{align-items:baseline;gap:var(--space-3);padding-bottom:var(--space-1);margin-bottom:var(--space-1);flex-wrap:wrap;display:flex}.group-head h2,.group-head h3{font-size:var(--fs-secondary);font-weight:600}.group-head .btn{margin-left:auto}.session-grid{gap:var(--space-4);margin-bottom:var(--space-4);grid-template-columns:repeat(auto-fill,minmax(340px,1fr));display:grid}.session-card{border:var(--hairline);border-radius:var(--radius);min-width:0;padding:var(--space-4);gap:var(--space-2);flex-direction:column;transition:border-color .2s;display:flex}.session-card:hover{border-color:var(--text)}.session-card.attention{border-color:var(--status-attention)}.session-card.error{border-color:var(--status-error)}.session-title{justify-content:space-between;align-items:baseline;gap:var(--space-2);min-width:0;display:flex}.session-title .name{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-weight:500;overflow:hidden}.session-project{font-size:var(--fs-small);color:var(--muted);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.attention-reason{font-weight:600;font-size:var(--fs-secondary)}.error-reason{color:var(--status-error);font-size:var(--fs-secondary)}.activity{font-size:var(--fs-secondary);color:var(--muted);-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.subagents{border-left:var(--hairline);padding-left:var(--space-3);font-size:var(--fs-small);flex-direction:column;gap:2px;display:flex}.subagents .agent-line{color:var(--muted);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.subagents .agent-line .live{color:var(--status-running)}.session-meta{font-size:var(--fs-small);color:var(--muted);gap:var(--space-1);flex-wrap:wrap;display:flex}.session-actions{gap:var(--space-2);padding-top:var(--space-2);flex-wrap:wrap;margin-top:auto;display:flex}.disk-row{align-items:center;gap:var(--space-3);border-bottom:var(--hairline);font-size:var(--fs-secondary);padding:2px 0;display:flex}.disk-row .name{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.disk-row .meta{color:var(--muted);font-size:var(--fs-small);white-space:nowrap;margin-left:auto}.disk-row .actions{gap:var(--space-2);flex-shrink:0;display:flex}.disk-more{margin-top:var(--space-1);font-size:var(--fs-small);color:var(--muted);cursor:pointer;background:0 0;border:none;padding:0;font-family:inherit;text-decoration:underline;display:inline-block}.disk-more:hover{color:var(--text)}.empty-state{border:var(--hairline);border-radius:var(--radius);padding:var(--space-6);text-align:center;color:var(--muted);margin-top:var(--space-6)}.session-screen{flex-direction:column;width:100%;min-width:0;max-width:100%;height:100dvh;display:flex;overflow:hidden}.session-bar{border-bottom:var(--hairline);flex-shrink:0}.session-bar-inner{width:100%;min-width:0;max-width:960px;padding:var(--space-2) var(--space-4);align-items:center;gap:var(--space-3);flex-wrap:wrap;margin:0 auto;display:flex}.session-bar .back{white-space:nowrap;text-decoration:none}.session-bar .title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-weight:500;overflow:hidden}.session-bar .project,.session-bar .agent-type{color:var(--muted);font-size:var(--fs-small);white-space:nowrap}.session-bar .right{align-items:center;gap:var(--space-2);flex-wrap:wrap;min-width:0;margin-left:auto;display:flex}.session-info-bar{font-size:var(--fs-small);color:var(--muted);justify-content:space-between;padding-top:0}.session-info-left{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.session-info-right{gap:var(--space-2);flex-wrap:wrap;justify-content:flex-end;margin-left:auto;display:flex}.stats-trigger{color:var(--muted);font:inherit;cursor:pointer;background:0 0;border:none;padding:0}.stats-popover{right:max(var(--space-4), calc((100vw - 960px) / 2 + var(--space-4)));z-index:10;border:var(--hairline);border-radius:var(--radius);background:var(--bg);padding:var(--space-3);min-width:280px;position:absolute;top:78px;box-shadow:0 12px 32px #0000002e}.stats-grid{gap:var(--space-1) var(--space-3);font-size:var(--fs-small);grid-template-columns:1fr auto;display:grid}.stats-grid span{color:var(--muted)}.session-info-right span+span:before{content:"·";margin-right:var(--space-2);color:var(--muted)}.switcher{min-width:0;padding:1px var(--space-2);border:var(--hairline);border-radius:var(--radius);font-size:var(--fs-small);background:var(--bg);color:var(--text);cursor:pointer;white-space:nowrap;align-items:center;gap:6px;font-family:inherit;transition:border-color .2s;display:inline-flex}.switcher:hover{border-color:var(--text)}.switcher .label{color:var(--muted)}.switcher .value{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.model-switcher{max-width:min(340px,45vw)}.session-bar.collapsed{background:var(--bg)}.session-bar-main .chrome-toggle{margin-left:auto}.chrome-toggle{border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--muted);font:inherit;font-size:var(--fs-small);padding:1px var(--space-2);cursor:pointer;white-space:nowrap}.chrome-toggle:hover{border-color:var(--text);color:var(--text)}.chat{flex:1;min-height:0;overflow-y:auto}.chat-inner{max-width:960px;padding:var(--space-4);gap:var(--space-4);flex-direction:column;margin:0 auto;display:flex}.assistant-turn{border-left:var(--hairline);padding-left:var(--space-3);gap:var(--space-3);flex-direction:column;display:flex}.entry{min-width:0}.entry-head{font-size:var(--fs-small);color:var(--muted);margin-bottom:var(--space-1);gap:var(--space-2);align-items:baseline;display:flex}.entry-action{color:var(--muted);font:inherit;font-size:var(--fs-small);cursor:pointer;opacity:.65;background:0 0;border:none;padding:0}.entry-action:hover{opacity:1;text-decoration:underline}.skill-badge,.queued-kind{border:var(--hairline);border-radius:var(--radius);padding:0 var(--space-1);font-size:var(--fs-small);color:var(--muted)}.entry.user{align-self:flex-end;max-width:85%}.entry.user .entry-body{border:1px solid var(--text);border-radius:var(--radius);padding:var(--space-2) var(--space-3);white-space:pre-wrap}.entry.user .entry-head{justify-content:flex-end}.entry.assistant{max-width:100%}.entry.assistant .entry-body{white-space:normal}.markdown-body{color:var(--text)}.markdown-body,.markdown-body *{-webkit-text-fill-color:currentColor}.markdown-body p,.markdown-body ul,.markdown-body ol,.markdown-body pre,.markdown-body blockquote,.markdown-body table{margin:0 0 var(--space-2)}.markdown-body :last-child{margin-bottom:0}.markdown-body ul,.markdown-body ol{padding-left:var(--space-5)}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4{font-size:var(--fs-h3);margin:var(--space-2) 0 var(--space-1);font-weight:500}.markdown-body code{border:var(--hairline);border-radius:var(--radius);font-size:var(--fs-secondary);padding:0 4px}.markdown-body pre{background:var(--bg);margin-top:var(--space-1)}.markdown-body pre code{border:none;padding:0}.markdown-body a{text-decoration:underline}.markdown-body table{border-collapse:collapse;width:100%;font-size:var(--fs-secondary)}.markdown-body th,.markdown-body td{border:var(--hairline);padding:var(--space-1) var(--space-2);text-align:left}.markdown-body blockquote{border-left:var(--hairline);padding-left:var(--space-3);color:var(--muted)}.entry-body code{border:var(--hairline);border-radius:var(--radius);font-size:var(--fs-secondary);padding:0 4px}details.thinking{border-left:var(--hairline);padding-left:var(--space-3);margin-bottom:var(--space-3)}details.thinking summary{cursor:pointer;font-size:var(--fs-small);color:var(--muted);-webkit-user-select:none;user-select:none;list-style:none}details.thinking summary:before{content:"▸ "}details.thinking[open] summary:before{content:"▾ "}details.thinking .thinking-body{color:var(--muted);font-size:var(--fs-secondary);padding-top:var(--space-1);white-space:pre-wrap}.tool{border:var(--hairline);border-radius:var(--radius);font-size:var(--fs-secondary)}.tool summary{cursor:pointer;-webkit-user-select:none;user-select:none;gap:var(--space-2);padding:var(--space-1) var(--space-3);align-items:baseline;list-style:none;display:flex}.tool summary:before{content:"▸";color:var(--muted)}.tool[open] summary:before{content:"▾"}.tool .tool-name{font-weight:600}.tool .tool-arg{color:var(--muted);text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.tool .tool-status{white-space:nowrap;font-size:var(--fs-small);color:var(--muted);margin-left:auto}.tool .tool-status.running{color:var(--status-running)}.tool .tool-status.error{color:var(--status-error)}.tool .tool-command{border-top:var(--hairline);padding:var(--space-2) var(--space-3) 0;overflow-x:auto}.tool .tool-command code{border:var(--hairline);border-radius:var(--radius);padding:var(--space-1) var(--space-2);white-space:pre-wrap;display:block}.tool .tool-input{border-top:var(--hairline);padding:var(--space-2) var(--space-3);overflow-x:auto}.tool .tool-input-label{font-size:var(--fs-small);color:var(--muted);margin-bottom:var(--space-1);display:block}.tool .tool-input pre{font-size:var(--fs-small);white-space:pre-wrap;border:none;padding:0}.tool .suggested-command{margin-top:var(--space-1)}.tool .suggested-command code{border:var(--hairline);border-radius:var(--radius);padding:1px var(--space-2)}.tool .tool-result{border-top:var(--hairline);padding:var(--space-2) var(--space-3);overflow-x:auto}.tool-output-truncated,.transcript-window-control{color:var(--muted);font-size:var(--fs-small);margin-bottom:var(--space-2)}.transcript-window-control{text-align:center}.tool .tool-command+.tool-result,.tool .tool-input+.tool-result{border-top:none}.tool .tool-result pre{font-size:var(--fs-small);border:none;padding:0}.tool .tool-result pre,.tool .tool-input pre{max-height:340px;overflow-y:auto}.hljs{color:var(--text);background:0 0}.hljs-comment,.hljs-quote{color:var(--syntax-comment)}.hljs-keyword,.hljs-selector-tag{color:var(--syntax-keyword)}.hljs-number,.hljs-literal{color:var(--syntax-number)}.hljs-string,.hljs-doctag{color:var(--syntax-string)}.hljs-function,.hljs-title,.hljs-title.function_,.hljs-section,.hljs-name{color:var(--syntax-function)}.hljs-type,.hljs-class,.hljs-title.class_,.hljs-built_in{color:var(--syntax-type)}.hljs-attr,.hljs-variable,.hljs-variable.language_,.hljs-params,.hljs-property{color:var(--syntax-variable)}.hljs-meta,.hljs-meta .hljs-keyword,.hljs-meta .hljs-string{color:var(--syntax-keyword)}.hljs-operator{color:var(--syntax-operator)}.hljs-punctuation{color:var(--syntax-punctuation)}.hljs-subst{color:var(--text)}.diff-add{color:var(--status-running)}.diff-del{color:var(--status-error)}.entry.summary-card .entry-body{border:var(--hairline);border-radius:var(--radius);padding:var(--space-2) var(--space-3);color:var(--muted);font-size:var(--fs-secondary)}.entry.summary-card summary{cursor:pointer;-webkit-user-select:none;user-select:none;color:var(--muted);font-size:var(--fs-secondary);list-style:none}.entry.summary-card summary:before{content:"▸ "}.entry.summary-card details[open] summary:before{content:"▾ "}.entry.custom-card .entry-body,.entry.agent-result-card details{border:var(--hairline);border-radius:var(--radius);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary)}.entry.agent-result-card summary{cursor:pointer;-webkit-user-select:none;user-select:none;gap:var(--space-2);color:var(--muted);align-items:baseline;list-style:none;display:flex}.entry.agent-result-card summary:before{content:"▸ "}.entry.agent-result-card details[open] summary:before{content:"▾ "}.agent-result-header{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.entry.agent-result-card .entry-body{margin-top:var(--space-2)}.streaming-cursor:after{content:"▌";color:var(--status-running)}.dock{border-top:var(--hairline);flex-shrink:0}.dock.collapsed{background:var(--bg)}.dock-collapse-row{max-width:960px;padding:var(--space-1) var(--space-4);align-items:center;gap:var(--space-3);font-size:var(--fs-small);color:var(--muted);margin:0 auto;display:flex}.dock-collapsed-hint{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.dock-inner{max-width:960px;padding:var(--space-3) var(--space-4);gap:var(--space-2);flex-direction:column;margin:0 auto;display:flex}.tasks{border:var(--hairline);border-radius:var(--radius);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary)}.tasks summary{cursor:pointer;-webkit-user-select:none;user-select:none;color:var(--muted);font-size:var(--fs-small);list-style:none}.tasks summary:before{content:"▸ "}.tasks[open] summary:before{content:"▾ "}.tasks ul{padding-top:var(--space-1);list-style:none}.tasks .done{color:var(--muted)}.tasks .active{font-weight:600}.subagent-strip{border:var(--hairline);border-radius:var(--radius);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary);align-items:center;gap:var(--space-3);flex-wrap:wrap;display:flex}.subagent-strip .count,.subagent-strip .collapsed-hint{color:var(--muted);font-size:var(--fs-small);white-space:nowrap}.subagent-strip .subagent-toggle{border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--text);font-family:inherit;font-size:var(--fs-small);padding:1px var(--space-2);cursor:pointer}.subagent-strip .subagent-toggle:hover{border-color:var(--text)}.subagent-strip.collapsed{padding-block:var(--space-1)}.subagent-strip .agent-chip{border:var(--hairline);border-radius:var(--radius);padding:1px var(--space-2);font-size:var(--fs-small);background:var(--bg);color:var(--text);cursor:pointer;align-items:center;gap:6px;max-width:260px;font-family:inherit;transition:border-color .2s;display:inline-flex}.subagent-strip .agent-chip:hover{border-color:var(--text)}.subagent-strip .agent-chip .live{color:var(--status-running)}.subagent-strip .agent-chip .done{color:var(--muted)}.subagent-strip .agent-chip .task{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.status-line{align-items:center;gap:var(--space-3);font-size:var(--fs-secondary);flex-wrap:wrap;display:flex}.status-line .working{color:var(--status-running)}.status-line .warning{color:var(--status-attention)}.status-line .error{color:var(--status-error)}.status-line .queued{color:var(--muted);font-size:var(--fs-small)}.status-line .btn{margin-left:auto}.status-line .inline-stop{margin-left:var(--space-2)}.composer{gap:var(--space-2);flex-direction:column;display:flex;position:relative}.queued-message-row{border:var(--hairline);border-radius:var(--radius);padding:var(--space-2);align-items:center;gap:var(--space-2);font-size:var(--fs-small);flex-wrap:wrap;display:flex}.queued-chip{align-items:baseline;gap:var(--space-1);text-overflow:ellipsis;white-space:nowrap;max-width:240px;color:var(--muted);display:inline-flex;overflow:hidden}.attachment-strip{gap:var(--space-2);flex-wrap:wrap;display:flex}.attachment-thumb,.attachment-file{border:var(--hairline);border-radius:var(--radius);padding:2px;position:relative}.attachment-file{align-items:baseline;gap:var(--space-2);padding:2px var(--space-2);font-size:var(--fs-small);overflow-wrap:anywhere;max-width:100%;display:inline-flex}.attachment-thumb img{object-fit:cover;width:72px;height:54px;display:block}.attachment-thumb button,.attachment-file button{border:var(--hairline);background:var(--bg);color:var(--text);cursor:pointer;border-radius:999px;position:absolute;top:-8px;right:-8px}.command-popover{left:0;right:0;bottom:calc(100% + var(--space-2));border:var(--hairline);border-radius:var(--radius);background:var(--bg);padding:var(--space-1);z-index:5;max-height:260px;position:absolute;overflow-y:auto}.command-option{background:var(--bg);width:100%;color:var(--text);font:inherit;text-align:left;padding:var(--space-2);border-radius:var(--radius);gap:var(--space-2);cursor:pointer;border:none;grid-template-columns:minmax(120px,max-content) 1fr max-content;align-items:baseline;display:grid}.command-option:hover,.command-option.selected{background:var(--text);color:var(--bg)}.command-name{font-weight:500}.command-description{color:var(--muted);font-size:var(--fs-small);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.command-source{border:var(--hairline);border-radius:var(--radius);color:var(--muted);font-size:var(--fs-small);padding:0 var(--space-1)}.command-option.selected .command-description,.command-option:hover .command-description,.command-option.selected .command-source,.command-option:hover .command-source{color:var(--bg)}.composer textarea{border:var(--hairline);border-radius:var(--radius);background:var(--bg);width:100%;color:var(--text);padding:var(--space-2) var(--space-3);font-size:var(--fs-body);resize:vertical;min-height:60px;max-height:40vh;font-family:inherit;line-height:1.6;transition:border-color .2s;overflow-y:hidden}.composer textarea:hover,.composer textarea:focus{border-color:var(--text);outline:none}.hidden-file-input{display:none}.composer-row{align-items:center;gap:var(--space-2);flex-wrap:wrap;display:flex}.mode-toggle{border:var(--hairline);border-radius:var(--radius);font-size:var(--fs-small);display:inline-flex;overflow:hidden}.mode-toggle button{background:var(--bg);color:var(--muted);padding:2px var(--space-3);cursor:pointer;border:none;font-family:inherit}.mode-toggle button.selected{background:var(--text);color:var(--bg)}.composer-row .send{margin-left:auto}.ghost-suggest{font-size:var(--fs-small);color:var(--muted);cursor:pointer;background:0 0;border:none;font-family:inherit}.ghost-suggest .key{border:var(--hairline);border-radius:var(--radius);padding:0 4px}.readonly-note{border:var(--hairline);border-radius:var(--radius);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary);color:var(--muted)}.fallback-banner{border:1px solid var(--status-attention);border-radius:var(--radius);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary);gap:var(--space-3);align-items:baseline;display:flex}.fallback-banner .dismiss{margin-left:auto}.widget-block{border:var(--hairline);border-radius:var(--radius);padding:var(--space-2) var(--space-3);font-size:var(--fs-small);color:var(--muted);white-space:pre-wrap}.toast-region{bottom:var(--space-4);right:var(--space-4);gap:var(--space-2);z-index:50;flex-direction:column;max-width:360px;display:flex;position:fixed}.toast{border:var(--hairline);border-radius:var(--radius);background:var(--bg);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary);gap:var(--space-2);align-items:baseline;display:flex}.toast.warning{border-color:var(--status-attention)}.toast.error{border-color:var(--status-error)}.files-head{justify-content:space-between;align-items:flex-start;gap:var(--space-4);margin:var(--space-5) 0 var(--space-4);flex-wrap:wrap;display:flex}.crumbs{font-size:var(--fs-secondary);flex-wrap:wrap;align-items:baseline;gap:2px;display:flex}.crumbs .sep{color:var(--muted);margin:0 2px}.scope-note{color:var(--muted);font-size:var(--fs-small);margin-top:var(--space-1)}.head-actions{gap:var(--space-2);flex-wrap:wrap;display:flex}.places{gap:var(--space-2);margin-bottom:var(--space-4);flex-wrap:wrap;align-items:baseline;display:flex}.places .label{color:var(--muted);font-size:var(--fs-small)}.place-chip{border:var(--hairline);border-radius:var(--radius);padding:1px var(--space-2);font-size:var(--fs-small);background:var(--bg);color:var(--text);cursor:pointer;font-family:inherit;text-decoration:none;transition:border-color .2s}.place-chip:hover{border-color:var(--text)}.file-table{border-collapse:collapse;width:100%;font-size:var(--fs-secondary)}.file-table th{text-align:left;font-size:var(--fs-small);color:var(--muted);border-bottom:var(--hairline);padding:var(--space-1) var(--space-2);font-weight:400}.file-table td{border-bottom:var(--hairline);padding:var(--space-2)}.file-table .name{color:var(--text);cursor:pointer;font-family:inherit;font-size:inherit;background:0 0;border:none;padding:0;text-decoration:none}.file-table .name .icon{color:var(--muted);margin-right:var(--space-1)}.file-table .meta{color:var(--muted);font-size:var(--fs-small);white-space:nowrap}.file-table .actions{text-align:right}.drop-zone{border:1px dashed var(--border);border-radius:var(--radius);padding:var(--space-4);text-align:center;color:var(--muted);font-size:var(--fs-small);margin-top:var(--space-4);transition:border-color .2s}.drop-zone.active{border-color:var(--text);color:var(--text)}.upload-warning{font-size:var(--fs-small);color:var(--muted);margin-top:var(--space-2)}.settings-wrap{max-width:720px}.settings-intro{color:var(--muted);font-size:var(--fs-secondary);margin:var(--space-2) 0 var(--space-5)}.settings-section{margin-bottom:var(--space-6)}.settings-section h2{font-size:var(--fs-h3);border-bottom:var(--hairline);padding-bottom:var(--space-2);margin-bottom:var(--space-3)}.setting-row{justify-content:space-between;align-items:center;gap:var(--space-4);padding:var(--space-2) 0;display:flex}.setting-label{flex-direction:column;gap:2px;min-width:0;display:flex}.setting-label .name{font-size:var(--fs-secondary)}.setting-label .hint{font-size:var(--fs-small);color:var(--muted)}.setting-control select,.setting-control input{border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--text);font-family:inherit;font-size:var(--fs-secondary);padding:2px var(--space-2)}.checkbox-control{align-items:center;gap:var(--space-2);font-size:var(--fs-secondary);display:inline-flex}.checkbox-control input{width:auto}.settings-error{border:1px solid var(--status-error);border-radius:var(--radius);color:var(--status-error);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary);margin-bottom:var(--space-4);white-space:pre-wrap}.settings-warning{border:1px solid var(--status-attention);border-radius:var(--radius);color:var(--status-attention);padding:var(--space-2) var(--space-3);font-size:var(--fs-secondary);margin-bottom:var(--space-4);white-space:pre-wrap}.model-picker-button{white-space:normal;overflow-wrap:anywhere;text-align:left;min-width:0;max-width:min(100%,520px)}.agent-model-row{gap:var(--space-3);padding:var(--space-2) 0;border-bottom:var(--hairline);grid-template-columns:minmax(0,1fr) minmax(0,1.2fr) auto;align-items:start;display:grid}.agent-model-summary{flex-direction:column;gap:2px;min-width:0;display:flex}.agent-model-name{font-size:var(--fs-secondary);font-weight:600}.agent-model-description{color:var(--muted);font-size:var(--fs-small)}.agent-model-fallbacks{gap:var(--space-1);flex-wrap:wrap;min-width:0;display:flex}.agent-model-chip{border:var(--hairline);border-radius:var(--radius);color:var(--muted);font-size:var(--fs-small);padding:0 var(--space-1);overflow-wrap:anywhere;white-space:normal;min-width:0;max-width:100%}.agent-model-editor{gap:var(--space-2);border:var(--hairline);border-radius:var(--radius);padding:var(--space-2);flex-direction:column;grid-column:1/-1;display:flex}.agent-model-entry{justify-content:space-between;gap:var(--space-2);font-size:var(--fs-secondary);align-items:center;min-width:0;display:flex}.agent-model-entry>span:first-child{overflow-wrap:anywhere;min-width:0}.agent-model-entry-actions{gap:var(--space-1);flex-shrink:0;display:flex}.settings-footnote{margin-top:calc(-1 * var(--space-3));margin-bottom:var(--space-5)}.device-row{align-items:center;gap:var(--space-3);padding:var(--space-2) 0;border-bottom:var(--hairline);font-size:var(--fs-secondary);display:flex}.device-row .meta{color:var(--muted);font-size:var(--fs-small);margin-left:auto}.this-device{border:var(--hairline);border-radius:var(--radius);font-size:var(--fs-small);color:var(--muted);padding:0 var(--space-1);margin-left:var(--space-1)}.pair-wrap{min-height:100vh;padding:var(--space-4);justify-content:center;align-items:center;display:flex}.pair-card{border:var(--hairline);border-radius:var(--radius);width:100%;max-width:460px;padding:var(--space-6);gap:var(--space-4);flex-direction:column;display:flex}.pair-card h1{font-size:var(--fs-h2)}.pair-card .sub{color:var(--muted);font-size:var(--fs-secondary)}.identity{gap:var(--space-2);border:var(--hairline);border-radius:var(--radius);padding:var(--space-3);font-size:var(--fs-secondary);flex-direction:column;display:flex}.identity .row-line{justify-content:space-between;gap:var(--space-3);display:flex}.identity .k{color:var(--muted);font-size:var(--fs-small)}.pin-input{text-align:center;width:100%;font-size:var(--fs-h1);letter-spacing:.5em;border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--text);padding:var(--space-2);font-family:inherit}.pin-input:focus{border-color:var(--text);outline:none}.pin-hint{font-size:var(--fs-small);color:var(--muted)}.security-note{border-top:var(--hairline);padding-top:var(--space-3);gap:var(--space-2);font-size:var(--fs-small);color:var(--muted);flex-direction:column;display:flex}.pair-error{color:var(--status-error);font-size:var(--fs-secondary)}.denied-card{border:1px solid var(--status-error);border-radius:var(--radius);width:100%;max-width:460px;padding:var(--space-6);gap:var(--space-3);flex-direction:column;display:flex}.field{gap:var(--space-1);flex-direction:column;display:flex}.field label{font-size:var(--fs-small);color:var(--muted)}.field input,.field textarea,.field select{border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--text);font-family:inherit;font-size:var(--fs-secondary);padding:var(--space-2)}.field input:focus,.field textarea:focus{border-color:var(--text);outline:none}.recent-projects{flex-direction:column;gap:2px;max-height:160px;display:flex;overflow-y:auto}.recent-projects button{text-align:left;color:var(--text);font-family:inherit;font-size:var(--fs-secondary);padding:var(--space-1) var(--space-2);cursor:pointer;border-radius:var(--radius);background:0 0;border:none}.recent-projects button:hover{background:var(--border)}.model-scope-tabs{border:var(--hairline);border-radius:var(--radius);margin-bottom:var(--space-3);font-size:var(--fs-small);display:inline-flex;overflow:hidden}.model-scope-tabs button{background:var(--bg);color:var(--muted);padding:2px var(--space-3);cursor:pointer;border:none;font-family:inherit}.model-scope-tabs button.selected{background:var(--text);color:var(--bg)}.model-picker-modal{min-width:min(360px,100vw - 32px);max-width:min(900px,100vw - 32px);overflow:auto}@media (width>=701px){.model-picker-modal{resize:horizontal}}.model-list{gap:var(--space-2);flex-direction:column;min-width:0;display:flex;overflow:hidden auto}.model-provider-group{flex-direction:column;gap:2px;display:flex}.model-provider-heading{background:var(--bg);color:var(--muted);font-size:var(--fs-small);border-bottom:var(--hairline);padding:var(--space-1) var(--space-2);z-index:1;position:sticky;top:0}.model-row{align-items:start;gap:2px var(--space-2);text-align:left;border:var(--hairline);border-radius:var(--radius);color:var(--text);font-family:inherit;font-size:var(--fs-secondary);padding:var(--space-1) var(--space-2);cursor:pointer;background:0 0;grid-template-columns:18px minmax(0,1fr) auto auto;min-width:0;display:grid}.model-row:hover,.model-row.current{border-color:var(--text)}.model-current{color:var(--status-running);grid-area:1/1}.model-id{overflow-wrap:anywhere;white-space:normal;grid-area:1/2;min-width:0;font-weight:600}.model-name{color:var(--muted);font-size:var(--fs-small);overflow-wrap:anywhere;white-space:normal;grid-area:2/2/auto/-1;min-width:0}.model-provider-badge,.model-reasoning{color:var(--muted);font-size:var(--fs-small);white-space:nowrap;border:var(--hairline);border-radius:var(--radius);padding:0 var(--space-1);grid-row:1}.model-provider-badge{grid-column:3}.model-reasoning{grid-column:4}.context-modal-body{gap:var(--space-4);flex-direction:column;display:flex}.context-section h3{font-size:var(--fs-secondary);margin-bottom:var(--space-1)}.context-section ul{gap:var(--space-1);font-size:var(--fs-small);flex-direction:column;list-style:none;display:flex}.context-section li{overflow-wrap:anywhere}.fork-message-list{gap:var(--space-2);flex-direction:column;max-height:360px;display:flex;overflow-y:auto}.fork-message{border:var(--hairline);border-radius:var(--radius);background:var(--bg);color:var(--text);font:inherit;text-align:left;padding:var(--space-2);cursor:pointer;gap:var(--space-2);display:flex}.fork-message:hover{border-color:var(--text)}.fork-entry-id{color:var(--muted);font-size:var(--fs-small);flex:none}@media (width<=700px){.container,.topbar-inner,.session-bar-inner,.dock-inner,.dock-collapse-row,.chat-inner{width:100%;min-width:0;max-width:100%;padding-left:var(--space-3);padding-right:var(--space-3)}.session-grid{grid-template-columns:minmax(0,1fr)}.session-card,.disk-row,.session-meta,.session-actions,.disk-row .actions{flex-wrap:wrap;min-width:0;max-width:100%}.disk-row .meta{order:3;width:100%;margin-left:0}.session-bar .project{display:none}.session-bar .right{align-items:stretch;width:100%;margin-left:0}.session-bar .right .switcher{justify-content:center}.session-bar .right .model-switcher{flex:100%;max-width:100%}.entry.user{max-width:100%}.file-table .cols{display:none}.setting-row{flex-direction:column;align-items:stretch}.setting-control{align-self:flex-end}.agent-model-row{grid-template-columns:1fr}.agent-model-edit{justify-self:start}.agent-model-entry{flex-direction:column;align-items:flex-start}.subagent-strip{align-items:stretch;gap:var(--space-2)}.subagent-strip .subagent-toggle{flex:none;min-height:32px}.subagent-strip .count,.subagent-strip .collapsed-hint{white-space:normal}.subagent-strip .agent-chip{max-width:100%}.head-actions .btn{flex:1}}
|