workerdeck 3.4.0 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.mjs +11 -2
- package/build/cli.mjs.map +1 -1
- package/build/index.d.mts +5 -0
- package/build/index.mjs +1 -1
- package/build/{instance-DyBeVgsK.mjs → instance-BMMh875G.mjs} +110 -5
- package/build/instance-BMMh875G.mjs.map +1 -0
- package/build/{reload-command-BB_HdGxV.mjs → reload-command-CTTfps7v.mjs} +2 -2
- package/build/{reload-command-BB_HdGxV.mjs.map → reload-command-CTTfps7v.mjs.map} +1 -1
- package/package.json +7 -5
- package/build/instance-DyBeVgsK.mjs.map +0 -1
package/build/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as ConfigError, d as parseArgs, f as resolveInstanceConfig, r as startInstance, u as loadConfigFile } from "./instance-
|
|
2
|
+
import { a as ConfigError, d as parseArgs, f as resolveInstanceConfig, r as startInstance, u as loadConfigFile } from "./instance-BMMh875G.mjs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -110,6 +110,15 @@ Options
|
|
|
110
110
|
has watched it for this long (30m, 2h; config:
|
|
111
111
|
engineSleepAfterMs). The session stays listed and
|
|
112
112
|
wakes on its next message. Default 'never'.
|
|
113
|
+
--agent-sleep-after <d>
|
|
114
|
+
the same for an agent's session, unless the agent sets
|
|
115
|
+
its own sleepAfterMs (30m, 2h, never; config:
|
|
116
|
+
agentSleepAfterMs). Default 15m.
|
|
117
|
+
--avatar-packs <list>
|
|
118
|
+
monkeyart packs new agent avatars are drawn from,
|
|
119
|
+
comma separated (monkey, toad, steampunk-bulldogs,
|
|
120
|
+
panda; config: avatarPacks). Default
|
|
121
|
+
monkey,steampunk-bulldogs,toad.
|
|
113
122
|
--effort-default <model>=<effort>
|
|
114
123
|
reasoning effort a session starts at, or switches to,
|
|
115
124
|
on this model (opus=high, gpt-6-sol=xhigh; repeatable;
|
|
@@ -194,7 +203,7 @@ function line(text) {
|
|
|
194
203
|
}
|
|
195
204
|
async function main(argv) {
|
|
196
205
|
if (argv[0] === "reload") {
|
|
197
|
-
const { runReload } = await import("./reload-command-
|
|
206
|
+
const { runReload } = await import("./reload-command-CTTfps7v.mjs");
|
|
198
207
|
return await runReload(argv.slice(1));
|
|
199
208
|
}
|
|
200
209
|
if (argv[0] === "relay") {
|
package/build/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/lib/shutdown.ts","../src/cli.ts"],"sourcesContent":["import type { Instance } from './instance.ts'\n\nexport type ShutdownOptions = {\n // A getter, not an instance: under --hot-reload the instance is replaced on every reload, and a captured one\n // would drain a server that stopped listening several generations ago.\n current: () => Instance\n // Runners this process holds outside any instance, which under --hot-reload is what a failed generation leaves\n // behind: no registry holds them, so `close()` cannot reach them and nothing else ends their engine children.\n alsoClose?: () => { close: (reason?: 'client' | 'server' | 'error') => void }[]\n log?: (text: string) => void\n}\n\nexport function installShutdown({\n current,\n alsoClose,\n log = (text) => void process.stdout.write(`${text}\\n`),\n}: ShutdownOptions): (signal: string) => void {\n // A second signal must always be able to kill a shutdown that is taking too long. That used to work only by\n // accident - the second call re-entered `instance.close()` and got an immediate callback out of an already-closed\n // http server - so it evaporated the moment close stopped being idempotent-by-luck. Make it a real path.\n let shuttingDown = false\n const shutdown = (signal: string): void => {\n if (shuttingDown) {\n process.stdout.write(`\\n[workerdeck] ${signal} again - terminating now\\n`)\n // Shell children are detached group leaders, so a plain exit orphans every one of them.\n current().server.shells?.killAllSync()\n process.exit(130)\n }\n shuttingDown = true\n log(`\\n[workerdeck] ${signal} - shutting down (press again to stop now)`)\n const instance = current()\n const running = instance.server.shells?.running() ?? []\n if (running.length > 0) {\n const named = running.map((shell) => `#${shell.ordinal} ${shell.label} (session ${shell.sessionId.slice(0, 8)})`).join(', ')\n log(`[workerdeck] ${running.length} shell(s) still running and will be stopped: ${named}`)\n }\n instance\n .drain({\n onProgress: (report) => {\n if (report.working.length > 0) {\n log(`[workerdeck] waiting for ${report.working.length} session(s) to finish the current turn`)\n }\n // Named, not waited for: nothing about shutting down answers a permission prompt.\n for (const id of report.awaitingHuman) {\n log(`[workerdeck] session ${id} is waiting on an approval - not waiting for it`)\n }\n if (report.timedOut) {\n log(`[workerdeck] ${report.working.length} session(s) still running - stopping anyway`)\n } else if (report.working.length === 0) {\n log('[workerdeck] all turns finished')\n }\n },\n })\n .catch(() => undefined)\n .then(() => instance.close())\n .then(() => {\n for (const runner of alsoClose?.() ?? []) {\n runner.close('server')\n }\n })\n .then(() => process.exit(0))\n .catch(() => process.exit(1))\n }\n process.on('SIGINT', () => shutdown('SIGINT'))\n process.on('SIGTERM', () => shutdown('SIGTERM'))\n // Returned for the one caller that cannot rely on the signal: a raw-mode TTY delivers ctrl-c as a byte, not SIGINT.\n return shutdown\n}\n","#!/usr/bin/env node\nimport { spawn } from 'node:child_process'\nimport { readFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { ConfigError, loadConfigFile, parseArgs, resolveInstanceConfig } from './config.ts'\nimport { startInstance } from './lib/instance.ts'\nimport { installShutdown } from './lib/shutdown.ts'\n\nconst HELP = `workerdeck - run a workerdeck instance: session gateway + dashboard, one port.\n\nUsage\n workerdeck [options]\n workerdeck guard [options] check whether it is safe to restart an instance\n workerdeck reload [options] hot-reload a gateway started with --hot-reload\n workerdeck relay <command> run or manage a cross-gateway peer relay (relay --help)\n\nOptions\n -p, --port <n> port to listen on (default 8787, WORKERDECK_PORT)\n --host <addr> interface to bind (default 127.0.0.1, WORKERDECK_HOST)\n --auth-key <secret> shared secret, min 12 chars; browsers log in with it,\n services send it as x-workerdeck-key\n (WORKERDECK_AUTH_KEY). Unset = no auth on loopback;\n on any other interface a key is generated instead,\n printed once, and stored in <state-dir>/auth-key for\n later starts to reuse.\n --trust-proxy trust x-forwarded-proto/-host/-for from one reverse\n proxy. Required behind TLS termination, or the session\n cookie loses its Secure flag and the origin check\n computes http:// where the browser says https://.\n --allowed-origin <o> extra origin accepted on browser requests, for when a\n proxy rewrites Host (repeatable)\n --allowed-host <name> extra Host header accepted when running without auth\n (repeatable; loopback names are always accepted)\n --insecure-host <name>\n bind host that may serve without auth - no key demanded,\n none generated - and, while unauthenticated, also\n accepted as a Host header (repeatable; config:\n insecureHosts). Names the host alone, no port.\n --profile <name=dir> Claude config dir a session may run under (repeatable). Declared\n this way a profile is fixed: it cannot be changed over the API.\n --profile-root <path> directory a profile created at runtime may point its config dir at\n (repeatable; default: your home directory). Narrow this to pin\n exactly which credential stores the dashboard may reach for.\n --no-profile-store refuse runtime profile management entirely - /v1/profiles stays\n read-only and profiles come from --profile and the config file\n alone. Profiles created at runtime are stored in\n <state-dir>/profiles.json, so --no-parking-store implies this.\n --cwd-root <path> restrict session cwds to this root (repeatable,\n WORKERDECK_CWD_ROOTS as a ':'-separated list)\n --fs-root <path> narrow which host directories /v1/fs serves\n (repeatable, WORKERDECK_FS_ROOTS as a ':'-separated\n list). Reading otherwise follows --cwd-root: a caller\n who may start a session in a tree can already read it\n through the agent. With neither, the routes 404.\n --fs-write also accept writes over /v1/fs. Its own switch because\n an agent's writes go through the permission flow and a\n PUT does not. Every write is still conditional on the\n hash the client last read.\n --shell let an operator run '$' shell commands on the host from\n the composer, in the session's cwd. Every one is a PTY\n with a tracked record. Off by default and its own switch:\n unlike an agent's Bash tool, a '$' command goes through\n no permission prompt at all. Never offered to a scoped\n principal. The agent gets shell_list and shell_read.\n --shell-agent-write <mode>\n what the agent may do to shells of its own with --shell:\n read-only (default: it only reads), gated (shell_run,\n shell_write and shell_kill behind a permission card,\n payload shown; config: shell.agentWrite) or allow (no\n card beyond the engine's own mode). Never a shell the\n user started, never a session a scoped principal made.\n --approval-timeout <d>\n how long a permission prompt or a question may wait for\n an answer before the engine denies it (300000, 30s, 5m;\n config: approvalTimeoutMs). Default 'none' - a prompt\n waits as long as the session lives. A session may\n override it per request.\n --engine-sleep-after <d>\n stop an idle session's engine process once no client\n has watched it for this long (30m, 2h; config:\n engineSleepAfterMs). The session stays listed and\n wakes on its next message. Default 'never'.\n --effort-default <model>=<effort>\n reasoning effort a session starts at, or switches to,\n on this model (opus=high, gpt-6-sol=xhigh; repeatable;\n config: effortDefaults). A profile's defaults.efforts\n wins per model. Default: the engine's own.\n --agent-context-reset <on|off|never>\n whether sessions get the context_reset tool, which\n lets an agent clear its own conversation between turns\n and continue from a prompt it wrote (config:\n agentContextReset). A request or profile may turn it\n on per session; 'never' forbids it. Default 'off'.\n --state-dir <path> where parked sessions are persisted\n (default: beside the config file, else ~/.workerdeck)\n --no-parking-store keep parked sessions in memory only; a restart drops them\n -c, --config <path> config file (default: ./workerdeck.config.mjs)\n --insecure allow no-auth on a non-loopback address. Only when\n something in front is doing the authenticating.\n --cors-origin <o> browser origin allowed to call /v1 cross-origin, for a\n dashboard served elsewhere (repeatable, exact origin, no\n wildcard; config: corsOrigins). Refused without auth. The\n key is still required - this only permits the call.\n --no-keep-awake don't hold the machine awake while a session is waiting\n on it (config: keepAwake: false). By default a turn in\n flight, or a session blocked on an approval, blocks idle\n sleep - a laptop sleeping mid-turn drops the socket and\n burns the turn. The screen is never kept on, and the\n lock releases the moment nothing is waiting.\n --no-web don't serve the web dashboard (config: web: false).\n /v1 and the auth routes are unchanged; everything else\n 404s. For a gateway reached only from the VS Code\n extension, the phone, or another host's dashboard.\n --open open the dashboard in a browser once it is up\n --hot-reload dev only: re-evaluate the gateway's own source in place on\n ctrl-r, SIGUSR2 or \\`workerdeck reload\\`, carrying live\n sessions and their engine child processes across the swap.\n Needs a source checkout; the published CLI is one bundled\n file with nothing to swap.\n -h, --help show this\n -v, --version print the version\n\nConfig file\n Options that cannot fit on a command line - \\`authenticate\\`, \\`buildRunnerConfig\\`,\n \\`createEngineRunner\\` are functions - live in workerdeck.config.mjs, which\n default-exports the createWorkerServer options (or a function returning them).\n Flags and env override it. Supplying your own \\`authenticate\\` turns the built-in\n shared-secret auth off entirely.\n\nCredentials\n workerdeck implements no Anthropic auth of its own: the official SDK/CLI\n resolves credentials from the environment, per profile. --auth-key protects this\n gateway, nothing else.\n`\n\nasync function readVersion(): Promise<string> {\n // src/cli.ts and build/cli.mjs are both one level under the package root.\n const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json')\n try {\n const raw = await readFile(pkgPath, 'utf8')\n return (JSON.parse(raw) as { version?: string }).version ?? 'unknown'\n } catch {\n return 'unknown'\n }\n}\n\nfunction openInBrowser(url: string): void {\n const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open'\n try {\n const child = spawn(command, [url], {\n stdio: 'ignore',\n detached: true,\n shell: process.platform === 'win32',\n })\n child.on('error', () => {})\n child.unref()\n } catch {}\n}\n\n// Read once and removed: every process this one spawns (engines, shells, the browser opener) inherits `process.env`.\nfunction takeGatewayEnv(): NodeJS.ProcessEnv {\n const env = { ...process.env }\n delete process.env.WORKERDECK_AUTH_KEY\n delete process.env.WORKERDECK_RELAY_KEY\n return env\n}\n\nfunction line(text: string): void {\n process.stdout.write(`${text}\\n`)\n}\n\nasync function main(argv: string[]): Promise<number> {\n if (argv[0] === 'reload') {\n const { runReload } = await import('./dev/reload-command.ts')\n return await runReload(argv.slice(1))\n }\n\n if (argv[0] === 'relay') {\n const { runRelayCli } = await import('@workerdeck/relay')\n return await runRelayCli(argv.slice(1))\n }\n\n if (argv[0] === 'guard') {\n const { runGuard } = await import('./lib/guard.ts')\n return await runGuard(argv.slice(1))\n }\n\n const env = takeGatewayEnv()\n const flags = parseArgs(argv)\n if (flags.help) {\n process.stdout.write(HELP)\n return 0\n }\n if (flags.version) {\n process.stdout.write(`${await readVersion()}\\n`)\n return 0\n }\n\n if (flags.hotReload) {\n const { runHotReload } = await import('./dev/hot-reload.ts')\n const result = await runHotReload(flags, env)\n if (result !== 'unsupported') {\n return result\n }\n }\n\n const loaded = await loadConfigFile(flags.config)\n const config = resolveInstanceConfig(flags, loaded, env)\n const instance = await startInstance(config)\n\n if (config.open) {\n openInBrowser(instance.url)\n }\n\n installShutdown({ current: () => instance, log: line })\n\n // Resolves only on close: until then the process stays up, serving.\n await instance.closed\n return 0\n}\n\nmain(process.argv.slice(2))\n .then((code) => {\n if (code !== 0) {\n process.exit(code)\n }\n })\n .catch((error: unknown) => {\n if (error instanceof ConfigError) {\n process.stderr.write(`[workerdeck] ${error.message}\\n`)\n process.exit(2)\n }\n process.stderr.write(`[workerdeck] ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\\n`)\n process.exit(1)\n })\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/lib/shutdown.ts","../src/cli.ts"],"sourcesContent":["import type { Instance } from './instance.ts'\n\nexport type ShutdownOptions = {\n // A getter, not an instance: under --hot-reload the instance is replaced on every reload, and a captured one\n // would drain a server that stopped listening several generations ago.\n current: () => Instance\n // Runners this process holds outside any instance, which under --hot-reload is what a failed generation leaves\n // behind: no registry holds them, so `close()` cannot reach them and nothing else ends their engine children.\n alsoClose?: () => { close: (reason?: 'client' | 'server' | 'error') => void }[]\n log?: (text: string) => void\n}\n\nexport function installShutdown({\n current,\n alsoClose,\n log = (text) => void process.stdout.write(`${text}\\n`),\n}: ShutdownOptions): (signal: string) => void {\n // A second signal must always be able to kill a shutdown that is taking too long. That used to work only by\n // accident - the second call re-entered `instance.close()` and got an immediate callback out of an already-closed\n // http server - so it evaporated the moment close stopped being idempotent-by-luck. Make it a real path.\n let shuttingDown = false\n const shutdown = (signal: string): void => {\n if (shuttingDown) {\n process.stdout.write(`\\n[workerdeck] ${signal} again - terminating now\\n`)\n // Shell children are detached group leaders, so a plain exit orphans every one of them.\n current().server.shells?.killAllSync()\n process.exit(130)\n }\n shuttingDown = true\n log(`\\n[workerdeck] ${signal} - shutting down (press again to stop now)`)\n const instance = current()\n const running = instance.server.shells?.running() ?? []\n if (running.length > 0) {\n const named = running.map((shell) => `#${shell.ordinal} ${shell.label} (session ${shell.sessionId.slice(0, 8)})`).join(', ')\n log(`[workerdeck] ${running.length} shell(s) still running and will be stopped: ${named}`)\n }\n instance\n .drain({\n onProgress: (report) => {\n if (report.working.length > 0) {\n log(`[workerdeck] waiting for ${report.working.length} session(s) to finish the current turn`)\n }\n // Named, not waited for: nothing about shutting down answers a permission prompt.\n for (const id of report.awaitingHuman) {\n log(`[workerdeck] session ${id} is waiting on an approval - not waiting for it`)\n }\n if (report.timedOut) {\n log(`[workerdeck] ${report.working.length} session(s) still running - stopping anyway`)\n } else if (report.working.length === 0) {\n log('[workerdeck] all turns finished')\n }\n },\n })\n .catch(() => undefined)\n .then(() => instance.close())\n .then(() => {\n for (const runner of alsoClose?.() ?? []) {\n runner.close('server')\n }\n })\n .then(() => process.exit(0))\n .catch(() => process.exit(1))\n }\n process.on('SIGINT', () => shutdown('SIGINT'))\n process.on('SIGTERM', () => shutdown('SIGTERM'))\n // Returned for the one caller that cannot rely on the signal: a raw-mode TTY delivers ctrl-c as a byte, not SIGINT.\n return shutdown\n}\n","#!/usr/bin/env node\nimport { spawn } from 'node:child_process'\nimport { readFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { ConfigError, loadConfigFile, parseArgs, resolveInstanceConfig } from './config.ts'\nimport { startInstance } from './lib/instance.ts'\nimport { installShutdown } from './lib/shutdown.ts'\n\nconst HELP = `workerdeck - run a workerdeck instance: session gateway + dashboard, one port.\n\nUsage\n workerdeck [options]\n workerdeck guard [options] check whether it is safe to restart an instance\n workerdeck reload [options] hot-reload a gateway started with --hot-reload\n workerdeck relay <command> run or manage a cross-gateway peer relay (relay --help)\n\nOptions\n -p, --port <n> port to listen on (default 8787, WORKERDECK_PORT)\n --host <addr> interface to bind (default 127.0.0.1, WORKERDECK_HOST)\n --auth-key <secret> shared secret, min 12 chars; browsers log in with it,\n services send it as x-workerdeck-key\n (WORKERDECK_AUTH_KEY). Unset = no auth on loopback;\n on any other interface a key is generated instead,\n printed once, and stored in <state-dir>/auth-key for\n later starts to reuse.\n --trust-proxy trust x-forwarded-proto/-host/-for from one reverse\n proxy. Required behind TLS termination, or the session\n cookie loses its Secure flag and the origin check\n computes http:// where the browser says https://.\n --allowed-origin <o> extra origin accepted on browser requests, for when a\n proxy rewrites Host (repeatable)\n --allowed-host <name> extra Host header accepted when running without auth\n (repeatable; loopback names are always accepted)\n --insecure-host <name>\n bind host that may serve without auth - no key demanded,\n none generated - and, while unauthenticated, also\n accepted as a Host header (repeatable; config:\n insecureHosts). Names the host alone, no port.\n --profile <name=dir> Claude config dir a session may run under (repeatable). Declared\n this way a profile is fixed: it cannot be changed over the API.\n --profile-root <path> directory a profile created at runtime may point its config dir at\n (repeatable; default: your home directory). Narrow this to pin\n exactly which credential stores the dashboard may reach for.\n --no-profile-store refuse runtime profile management entirely - /v1/profiles stays\n read-only and profiles come from --profile and the config file\n alone. Profiles created at runtime are stored in\n <state-dir>/profiles.json, so --no-parking-store implies this.\n --cwd-root <path> restrict session cwds to this root (repeatable,\n WORKERDECK_CWD_ROOTS as a ':'-separated list)\n --fs-root <path> narrow which host directories /v1/fs serves\n (repeatable, WORKERDECK_FS_ROOTS as a ':'-separated\n list). Reading otherwise follows --cwd-root: a caller\n who may start a session in a tree can already read it\n through the agent. With neither, the routes 404.\n --fs-write also accept writes over /v1/fs. Its own switch because\n an agent's writes go through the permission flow and a\n PUT does not. Every write is still conditional on the\n hash the client last read.\n --shell let an operator run '$' shell commands on the host from\n the composer, in the session's cwd. Every one is a PTY\n with a tracked record. Off by default and its own switch:\n unlike an agent's Bash tool, a '$' command goes through\n no permission prompt at all. Never offered to a scoped\n principal. The agent gets shell_list and shell_read.\n --shell-agent-write <mode>\n what the agent may do to shells of its own with --shell:\n read-only (default: it only reads), gated (shell_run,\n shell_write and shell_kill behind a permission card,\n payload shown; config: shell.agentWrite) or allow (no\n card beyond the engine's own mode). Never a shell the\n user started, never a session a scoped principal made.\n --approval-timeout <d>\n how long a permission prompt or a question may wait for\n an answer before the engine denies it (300000, 30s, 5m;\n config: approvalTimeoutMs). Default 'none' - a prompt\n waits as long as the session lives. A session may\n override it per request.\n --engine-sleep-after <d>\n stop an idle session's engine process once no client\n has watched it for this long (30m, 2h; config:\n engineSleepAfterMs). The session stays listed and\n wakes on its next message. Default 'never'.\n --agent-sleep-after <d>\n the same for an agent's session, unless the agent sets\n its own sleepAfterMs (30m, 2h, never; config:\n agentSleepAfterMs). Default 15m.\n --avatar-packs <list>\n monkeyart packs new agent avatars are drawn from,\n comma separated (monkey, toad, steampunk-bulldogs,\n panda; config: avatarPacks). Default\n monkey,steampunk-bulldogs,toad.\n --effort-default <model>=<effort>\n reasoning effort a session starts at, or switches to,\n on this model (opus=high, gpt-6-sol=xhigh; repeatable;\n config: effortDefaults). A profile's defaults.efforts\n wins per model. Default: the engine's own.\n --agent-context-reset <on|off|never>\n whether sessions get the context_reset tool, which\n lets an agent clear its own conversation between turns\n and continue from a prompt it wrote (config:\n agentContextReset). A request or profile may turn it\n on per session; 'never' forbids it. Default 'off'.\n --state-dir <path> where parked sessions are persisted\n (default: beside the config file, else ~/.workerdeck)\n --no-parking-store keep parked sessions in memory only; a restart drops them\n -c, --config <path> config file (default: ./workerdeck.config.mjs)\n --insecure allow no-auth on a non-loopback address. Only when\n something in front is doing the authenticating.\n --cors-origin <o> browser origin allowed to call /v1 cross-origin, for a\n dashboard served elsewhere (repeatable, exact origin, no\n wildcard; config: corsOrigins). Refused without auth. The\n key is still required - this only permits the call.\n --no-keep-awake don't hold the machine awake while a session is waiting\n on it (config: keepAwake: false). By default a turn in\n flight, or a session blocked on an approval, blocks idle\n sleep - a laptop sleeping mid-turn drops the socket and\n burns the turn. The screen is never kept on, and the\n lock releases the moment nothing is waiting.\n --no-web don't serve the web dashboard (config: web: false).\n /v1 and the auth routes are unchanged; everything else\n 404s. For a gateway reached only from the VS Code\n extension, the phone, or another host's dashboard.\n --open open the dashboard in a browser once it is up\n --hot-reload dev only: re-evaluate the gateway's own source in place on\n ctrl-r, SIGUSR2 or \\`workerdeck reload\\`, carrying live\n sessions and their engine child processes across the swap.\n Needs a source checkout; the published CLI is one bundled\n file with nothing to swap.\n -h, --help show this\n -v, --version print the version\n\nConfig file\n Options that cannot fit on a command line - \\`authenticate\\`, \\`buildRunnerConfig\\`,\n \\`createEngineRunner\\` are functions - live in workerdeck.config.mjs, which\n default-exports the createWorkerServer options (or a function returning them).\n Flags and env override it. Supplying your own \\`authenticate\\` turns the built-in\n shared-secret auth off entirely.\n\nCredentials\n workerdeck implements no Anthropic auth of its own: the official SDK/CLI\n resolves credentials from the environment, per profile. --auth-key protects this\n gateway, nothing else.\n`\n\nasync function readVersion(): Promise<string> {\n // src/cli.ts and build/cli.mjs are both one level under the package root.\n const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json')\n try {\n const raw = await readFile(pkgPath, 'utf8')\n return (JSON.parse(raw) as { version?: string }).version ?? 'unknown'\n } catch {\n return 'unknown'\n }\n}\n\nfunction openInBrowser(url: string): void {\n const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open'\n try {\n const child = spawn(command, [url], {\n stdio: 'ignore',\n detached: true,\n shell: process.platform === 'win32',\n })\n child.on('error', () => {})\n child.unref()\n } catch {}\n}\n\n// Read once and removed: every process this one spawns (engines, shells, the browser opener) inherits `process.env`.\nfunction takeGatewayEnv(): NodeJS.ProcessEnv {\n const env = { ...process.env }\n delete process.env.WORKERDECK_AUTH_KEY\n delete process.env.WORKERDECK_RELAY_KEY\n return env\n}\n\nfunction line(text: string): void {\n process.stdout.write(`${text}\\n`)\n}\n\nasync function main(argv: string[]): Promise<number> {\n if (argv[0] === 'reload') {\n const { runReload } = await import('./dev/reload-command.ts')\n return await runReload(argv.slice(1))\n }\n\n if (argv[0] === 'relay') {\n const { runRelayCli } = await import('@workerdeck/relay')\n return await runRelayCli(argv.slice(1))\n }\n\n if (argv[0] === 'guard') {\n const { runGuard } = await import('./lib/guard.ts')\n return await runGuard(argv.slice(1))\n }\n\n const env = takeGatewayEnv()\n const flags = parseArgs(argv)\n if (flags.help) {\n process.stdout.write(HELP)\n return 0\n }\n if (flags.version) {\n process.stdout.write(`${await readVersion()}\\n`)\n return 0\n }\n\n if (flags.hotReload) {\n const { runHotReload } = await import('./dev/hot-reload.ts')\n const result = await runHotReload(flags, env)\n if (result !== 'unsupported') {\n return result\n }\n }\n\n const loaded = await loadConfigFile(flags.config)\n const config = resolveInstanceConfig(flags, loaded, env)\n const instance = await startInstance(config)\n\n if (config.open) {\n openInBrowser(instance.url)\n }\n\n installShutdown({ current: () => instance, log: line })\n\n // Resolves only on close: until then the process stays up, serving.\n await instance.closed\n return 0\n}\n\nmain(process.argv.slice(2))\n .then((code) => {\n if (code !== 0) {\n process.exit(code)\n }\n })\n .catch((error: unknown) => {\n if (error instanceof ConfigError) {\n process.stderr.write(`[workerdeck] ${error.message}\\n`)\n process.exit(2)\n }\n process.stderr.write(`[workerdeck] ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\\n`)\n process.exit(1)\n })\n"],"mappings":"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"}
|
package/build/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Authenticator, CarriedSession, ShellAgentWriteOption, WorkerServer, WorkerServerOptions } from "@workerdeck/server";
|
|
2
2
|
import { KeyObject } from "node:crypto";
|
|
3
3
|
import { ProfileInfo, SessionNotification, SessionNotificationType, SubagentInfo } from "@workerdeck/protocol";
|
|
4
|
+
import { PackId } from "@monkeyart/packs";
|
|
4
5
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
5
6
|
//#region src/auth/auth-key.d.ts
|
|
6
7
|
type MaterializedAuthKey = {
|
|
@@ -104,6 +105,7 @@ type WorkerDeckConfig = WorkerServerOptions & {
|
|
|
104
105
|
keepAwake?: boolean;
|
|
105
106
|
corsOrigins?: string[];
|
|
106
107
|
apns?: ApnsConfig;
|
|
108
|
+
avatarPacks?: string[];
|
|
107
109
|
};
|
|
108
110
|
type CliFlags = {
|
|
109
111
|
config?: string;
|
|
@@ -124,6 +126,8 @@ type CliFlags = {
|
|
|
124
126
|
trustProxy?: boolean;
|
|
125
127
|
approvalTimeoutMs?: number | null;
|
|
126
128
|
engineSleepAfterMs?: number;
|
|
129
|
+
agentSleepAfterMs?: number;
|
|
130
|
+
avatarPacks?: string[];
|
|
127
131
|
effortDefaults?: Record<string, string>;
|
|
128
132
|
agentContextReset?: 'on' | 'off' | 'never';
|
|
129
133
|
stateDir?: string;
|
|
@@ -159,6 +163,7 @@ type ResolvedConfig = {
|
|
|
159
163
|
web: boolean;
|
|
160
164
|
keepAwake: boolean;
|
|
161
165
|
corsOrigins: string[];
|
|
166
|
+
avatarPacks?: PackId[];
|
|
162
167
|
apns?: ApnsConfig;
|
|
163
168
|
open: boolean;
|
|
164
169
|
profileStore: boolean;
|
package/build/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { n as runGuard } from "./guard-C4ybpzbt.mjs";
|
|
2
|
-
import { C as createActivityRegistry, S as createDeviceRoute, _ as createApnsForwarder, a as ConfigError, b as createApnsRoute, c as isLoopback, d as parseArgs, f as resolveInstanceConfig, g as buildPush, h as materializeAuthKey, i as renderLoginPage, l as isLoopbackHostname, m as createAuthSessionStore, n as resolveWebRoot, o as defaultStateDir, p as createCliAuth, r as startInstance, s as hostnameOf, t as createHostGuard, u as loadConfigFile, v as createApnsClient, w as createActivityRoute, x as createDeviceRegistry, y as loadApnsKey } from "./instance-
|
|
2
|
+
import { C as createActivityRegistry, S as createDeviceRoute, _ as createApnsForwarder, a as ConfigError, b as createApnsRoute, c as isLoopback, d as parseArgs, f as resolveInstanceConfig, g as buildPush, h as materializeAuthKey, i as renderLoginPage, l as isLoopbackHostname, m as createAuthSessionStore, n as resolveWebRoot, o as defaultStateDir, p as createCliAuth, r as startInstance, s as hostnameOf, t as createHostGuard, u as loadConfigFile, v as createApnsClient, w as createActivityRoute, x as createDeviceRegistry, y as loadApnsKey } from "./instance-BMMh875G.mjs";
|
|
3
3
|
export { ConfigError, buildPush, createActivityRegistry, createActivityRoute, createApnsClient, createApnsForwarder, createApnsRoute, createAuthSessionStore, createCliAuth, createDeviceRegistry, createDeviceRoute, createHostGuard, defaultStateDir, hostnameOf, isLoopback, isLoopbackHostname, loadApnsKey, loadConfigFile, materializeAuthKey, parseArgs, renderLoginPage, resolveInstanceConfig, resolveWebRoot, runGuard, startInstance };
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { createReadStream, existsSync, statSync } from "node:fs";
|
|
2
2
|
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
3
|
-
import { createFileProfileStore, createFileSessionStore, createFileSpendStore, createWorkerServer, readJsonOr, writeJsonAtomic } from "@workerdeck/server";
|
|
3
|
+
import { createFileAgentStore, createFileProfileStore, createFileSessionStore, createFileSpendStore, createWorkerServer, readJsonOr, writeJsonAtomic } from "@workerdeck/server";
|
|
4
4
|
import { dashboardDir } from "@workerdeck/web";
|
|
5
5
|
import { createHash, createHmac, createPrivateKey, randomBytes, sign, timingSafeEqual } from "node:crypto";
|
|
6
6
|
import { chmod, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
7
7
|
import { connect, constants } from "node:http2";
|
|
8
|
-
import { CHECKLIST_TEXT_MAX, isAgentRecord, parseUserQuestions, subagentLabel } from "@workerdeck/protocol";
|
|
8
|
+
import { CHECKLIST_TEXT_MAX, isAgentRecord, parseUserQuestions, projectAccent, subagentLabel } from "@workerdeck/protocol";
|
|
9
|
+
import { defaultPackId, packIds, packUrl } from "@monkeyart/packs";
|
|
10
|
+
import { createRecipe, decodePack, resolveRecipe, toAnimationStripPng, toPng } from "@monkeyart/composer";
|
|
9
11
|
import { homedir } from "node:os";
|
|
10
12
|
import { pathToFileURL } from "node:url";
|
|
11
13
|
import { spawn } from "node:child_process";
|
|
@@ -1728,6 +1730,93 @@ function wantsJson(req) {
|
|
|
1728
1730
|
return (req.headers.accept ?? "").includes("application/json");
|
|
1729
1731
|
}
|
|
1730
1732
|
//#endregion
|
|
1733
|
+
//#region src/lib/avatars.ts
|
|
1734
|
+
const EMBLEMS = {
|
|
1735
|
+
claude: "claude",
|
|
1736
|
+
codex: "codex",
|
|
1737
|
+
provider: "rings"
|
|
1738
|
+
};
|
|
1739
|
+
const DEFAULT_AVATAR_PACKS = [
|
|
1740
|
+
"monkey",
|
|
1741
|
+
"steampunk-bulldogs",
|
|
1742
|
+
"toad"
|
|
1743
|
+
];
|
|
1744
|
+
function isPackId(id) {
|
|
1745
|
+
return packIds.includes(id);
|
|
1746
|
+
}
|
|
1747
|
+
var MonkeyartAvatars = class {
|
|
1748
|
+
#packs;
|
|
1749
|
+
#loaded = /* @__PURE__ */ new Map();
|
|
1750
|
+
#cache = /* @__PURE__ */ new Map();
|
|
1751
|
+
constructor(packs = DEFAULT_AVATAR_PACKS) {
|
|
1752
|
+
this.#packs = packs.length ? packs : [defaultPackId];
|
|
1753
|
+
}
|
|
1754
|
+
async roll(seed, engine, projectKey) {
|
|
1755
|
+
const id = this.#packFor(seed);
|
|
1756
|
+
const pack = await this.#load(id);
|
|
1757
|
+
const emblem = EMBLEMS[engine ?? "claude"];
|
|
1758
|
+
return {
|
|
1759
|
+
pack: id,
|
|
1760
|
+
recipe: createRecipe(pack, {
|
|
1761
|
+
seed,
|
|
1762
|
+
emblem: pack.emblems.some((e) => e.id === emblem) ? emblem : null,
|
|
1763
|
+
baseColor: projectAccent(projectKey ?? seed),
|
|
1764
|
+
backdrop: "blend"
|
|
1765
|
+
})
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
async still(recipe) {
|
|
1769
|
+
return this.#render(recipe, "still", async (pack, resolved) => ({ png: await toPng(pack, resolved) }));
|
|
1770
|
+
}
|
|
1771
|
+
async busy(recipe) {
|
|
1772
|
+
const { pack } = this.#stored(recipe);
|
|
1773
|
+
if (!(await this.#load(pack)).animations.some((animation) => animation.id === "busy")) return;
|
|
1774
|
+
return this.#render(recipe, "busy", async (p, resolved) => {
|
|
1775
|
+
const strip = await toAnimationStripPng(p, resolved, "busy");
|
|
1776
|
+
return {
|
|
1777
|
+
png: strip.png,
|
|
1778
|
+
durations: strip.durations
|
|
1779
|
+
};
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
#packFor(seed) {
|
|
1783
|
+
const prefix = seed.slice(0, seed.indexOf(":"));
|
|
1784
|
+
if (isPackId(prefix)) return prefix;
|
|
1785
|
+
const digest = createHash("sha256").update(seed).digest();
|
|
1786
|
+
return this.#packs[digest.readUInt32BE(0) % this.#packs.length];
|
|
1787
|
+
}
|
|
1788
|
+
#stored(recipe) {
|
|
1789
|
+
const held = recipe;
|
|
1790
|
+
if (held && typeof held === "object" && typeof held.pack === "string" && isPackId(held.pack) && held.recipe) return held;
|
|
1791
|
+
return {
|
|
1792
|
+
pack: defaultPackId,
|
|
1793
|
+
recipe
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
async #render(recipe, kind, draw) {
|
|
1797
|
+
const stored = this.#stored(recipe);
|
|
1798
|
+
const pack = await this.#load(stored.pack);
|
|
1799
|
+
const key = `${kind}:${pack.checksum}:${JSON.stringify(stored.recipe)}`;
|
|
1800
|
+
const held = this.#cache.get(key);
|
|
1801
|
+
if (held) return held;
|
|
1802
|
+
const { recipe: resolved } = resolveRecipe(pack, stored.recipe);
|
|
1803
|
+
const image = {
|
|
1804
|
+
...await draw(pack, resolved),
|
|
1805
|
+
etag: `"${createHash("sha256").update(key).digest("hex").slice(0, 32)}"`
|
|
1806
|
+
};
|
|
1807
|
+
this.#cache.set(key, image);
|
|
1808
|
+
return image;
|
|
1809
|
+
}
|
|
1810
|
+
#load(id) {
|
|
1811
|
+
let pack = this.#loaded.get(id);
|
|
1812
|
+
if (!pack) {
|
|
1813
|
+
pack = readFile(packUrl(id)).then((bytes) => decodePack(new Uint8Array(bytes)));
|
|
1814
|
+
this.#loaded.set(id, pack);
|
|
1815
|
+
}
|
|
1816
|
+
return pack;
|
|
1817
|
+
}
|
|
1818
|
+
};
|
|
1819
|
+
//#endregion
|
|
1731
1820
|
//#region src/config.ts
|
|
1732
1821
|
const CONFIG_BASENAMES = [
|
|
1733
1822
|
"workerdeck.config.mjs",
|
|
@@ -1768,6 +1857,8 @@ const VALUED = /* @__PURE__ */ new Map([
|
|
|
1768
1857
|
["--insecure-host", (f, v) => f.insecureHosts.push(v)],
|
|
1769
1858
|
["--approval-timeout", (f, v, name) => f.approvalTimeoutMs = parseDuration(v, name)],
|
|
1770
1859
|
["--engine-sleep-after", (f, v, name) => f.engineSleepAfterMs = parseDuration(v, name) ?? 0],
|
|
1860
|
+
["--agent-sleep-after", (f, v, name) => f.agentSleepAfterMs = parseDuration(v, name) ?? 0],
|
|
1861
|
+
["--avatar-packs", (f, v, name) => f.avatarPacks = parseAvatarPacks(v, name)],
|
|
1771
1862
|
["--effort-default", (f, v, name) => f.effortDefaults = {
|
|
1772
1863
|
...f.effortDefaults,
|
|
1773
1864
|
...parseEffortDefault(v, name)
|
|
@@ -1944,7 +2035,7 @@ function resolveInstanceConfig(flags, loaded, env = process.env, cwd = process.c
|
|
|
1944
2035
|
if (parsed.origin !== origin) throw new ConfigError(`--cors-origin expects scheme://host[:port] with no path, got: ${origin}`);
|
|
1945
2036
|
}
|
|
1946
2037
|
if (!web && flags.open) throw new ConfigError("--open cannot be used with --no-web: there is no dashboard to open");
|
|
1947
|
-
const { port: _p, host: _h, auth: _a, stateDir: _s, allowedHosts: _ah, insecureHosts: _ih, webRoot: _w, web: _web, keepAwake: _ka, corsOrigins: _cors, apns: _ap, ...serverOptions } = loaded.options;
|
|
2038
|
+
const { port: _p, host: _h, auth: _a, stateDir: _s, allowedHosts: _ah, insecureHosts: _ih, webRoot: _w, web: _web, keepAwake: _ka, corsOrigins: _cors, apns: _ap, avatarPacks: _packs, ...serverOptions } = loaded.options;
|
|
1948
2039
|
const options = { ...serverOptions };
|
|
1949
2040
|
if (cwdRoots?.length) options.allowedCwdRoots = cwdRoots;
|
|
1950
2041
|
if (fsRoots?.length || flags.fsWrite) options.hostFiles = {
|
|
@@ -1965,6 +2056,7 @@ function resolveInstanceConfig(flags, loaded, env = process.env, cwd = process.c
|
|
|
1965
2056
|
if (flags.profiles.length) options.profiles = flags.profiles;
|
|
1966
2057
|
if (flags.approvalTimeoutMs !== void 0) options.approvalTimeoutMs = flags.approvalTimeoutMs;
|
|
1967
2058
|
if (flags.engineSleepAfterMs !== void 0) options.engineSleepAfterMs = flags.engineSleepAfterMs;
|
|
2059
|
+
if (flags.agentSleepAfterMs !== void 0) options.agentSleepAfterMs = flags.agentSleepAfterMs;
|
|
1968
2060
|
if (flags.agentContextReset === "never") options.agentContextReset = false;
|
|
1969
2061
|
else if (flags.agentContextReset !== void 0) options.agentContextReset = {
|
|
1970
2062
|
...loaded.options.agentContextReset,
|
|
@@ -1992,6 +2084,7 @@ function resolveInstanceConfig(flags, loaded, env = process.env, cwd = process.c
|
|
|
1992
2084
|
web,
|
|
1993
2085
|
keepAwake,
|
|
1994
2086
|
corsOrigins,
|
|
2087
|
+
avatarPacks: resolveAvatarPacks(flags.avatarPacks ?? loaded.options.avatarPacks),
|
|
1995
2088
|
apns: resolveApns(loaded),
|
|
1996
2089
|
open: flags.open ?? false,
|
|
1997
2090
|
profileStore,
|
|
@@ -2013,6 +2106,15 @@ function resolveApns(loaded) {
|
|
|
2013
2106
|
keyFile: resolve(base, apns.keyFile)
|
|
2014
2107
|
};
|
|
2015
2108
|
}
|
|
2109
|
+
function parseAvatarPacks(value, name) {
|
|
2110
|
+
const packs = value.split(",").map((pack) => pack.trim()).filter(Boolean);
|
|
2111
|
+
for (const pack of packs) if (!isPackId(pack)) throw new ConfigError(`${name}: unknown avatar pack "${pack}" (known: ${packIds.join(", ")})`);
|
|
2112
|
+
return packs;
|
|
2113
|
+
}
|
|
2114
|
+
function resolveAvatarPacks(packs) {
|
|
2115
|
+
if (packs === void 0) return;
|
|
2116
|
+
return parseAvatarPacks(packs.join(","), "avatarPacks");
|
|
2117
|
+
}
|
|
2016
2118
|
//#endregion
|
|
2017
2119
|
//#region src/auth/login-page.ts
|
|
2018
2120
|
function escapeHtml(value) {
|
|
@@ -2384,14 +2486,16 @@ async function startInstance(config, options = {}) {
|
|
|
2384
2486
|
authenticate: authenticatePush
|
|
2385
2487
|
});
|
|
2386
2488
|
const fallback = createFallback(auth, webRoot, hostAllowed, apns?.handleRequest ?? createApnsRoute(null, authenticatePush));
|
|
2387
|
-
const { parking, profileStore, shell, spend } = stateDirDefaults(config);
|
|
2489
|
+
const { agentStore, parking, profileStore, shell, spend } = stateDirDefaults(config);
|
|
2388
2490
|
const server = createWorkerServer({
|
|
2389
2491
|
...config.options,
|
|
2390
2492
|
...profileStore ? { profileStore } : {},
|
|
2493
|
+
agentStore,
|
|
2391
2494
|
checkCredentials: config.options.checkCredentials ?? true,
|
|
2392
2495
|
parking,
|
|
2393
2496
|
shell,
|
|
2394
2497
|
spend,
|
|
2498
|
+
avatars: config.options.avatars ?? new MonkeyartAvatars(config.avatarPacks),
|
|
2395
2499
|
notifications: apns === void 0 ? config.options.notifications : {
|
|
2396
2500
|
...config.options.notifications,
|
|
2397
2501
|
onNotification: (notification) => {
|
|
@@ -2494,6 +2598,7 @@ function stateDirDefaults(config) {
|
|
|
2494
2598
|
parking,
|
|
2495
2599
|
shell,
|
|
2496
2600
|
spend,
|
|
2601
|
+
agentStore: config.options.agentStore ?? createFileAgentStore(join(stateDir, "agents.json")),
|
|
2497
2602
|
...profileStore ? { profileStore } : {}
|
|
2498
2603
|
};
|
|
2499
2604
|
}
|
|
@@ -2517,4 +2622,4 @@ function bannerLines({ url, config, generated, authEnabled, push }) {
|
|
|
2517
2622
|
//#endregion
|
|
2518
2623
|
export { createActivityRegistry as C, createDeviceRoute as S, createApnsForwarder as _, ConfigError as a, createApnsRoute as b, isLoopback as c, parseArgs as d, resolveInstanceConfig as f, buildPush as g, materializeAuthKey as h, renderLoginPage as i, isLoopbackHostname as l, createAuthSessionStore as m, resolveWebRoot as n, defaultStateDir as o, createCliAuth as p, startInstance as r, hostnameOf as s, createHostGuard as t, loadConfigFile as u, createApnsClient as v, createActivityRoute as w, createDeviceRegistry as x, loadApnsKey as y };
|
|
2519
2624
|
|
|
2520
|
-
//# sourceMappingURL=instance-
|
|
2625
|
+
//# sourceMappingURL=instance-BMMh875G.mjs.map
|