* Internal helper to build config from already-resolved ports. * @param {{backendPort: number, vscodePort: number}} ports * @param {string} cwd * @param {Record } env * @returns {SafeDevConfig}
(ports, cwd, env)
| 609 | * @returns {SafeDevConfig} |
| 610 | */ |
| 611 | function buildConfigFromPorts(ports, cwd, env) { |
| 612 | const { backendPort, vscodePort } = ports; |
| 613 | const stateDir = path.resolve( |
| 614 | cwd, |
| 615 | env.OH_CANVAS_SAFE_STATE_DIR || |
| 616 | path.join(homedir(), ".openhands", "agent-canvas"), |
| 617 | ); |
| 618 | const conversationsPath = path.join(stateDir, "dev_conversations"); |
| 619 | const workspacesPath = path.join(stateDir, "workspaces"); |
| 620 | // Use provided secret key, or read/generate one persisted to |
| 621 | // ~/.openhands/agent-canvas/secret-key.txt. Persisting ensures dev mode |
| 622 | // and Docker mode share the same encryption key when they mount the same |
| 623 | // ~/.openhands directory (docker/entrypoint.sh reads/writes the same file). |
| 624 | const secretKeyPath = env.OH_SECRET_KEY_PATH || DEFAULT_SECRET_KEY_PATH; |
| 625 | const secretKey = |
| 626 | env.OH_SECRET_KEY || getOrCreatePersistedApiKey(secretKeyPath, "secret"); |
| 627 | // Use the user-provided LOCAL_BACKEND_API_KEY or fall back to a key |
| 628 | // persisted to ~/.openhands/agent-canvas/api-key.txt. Persisting on disk |
| 629 | // keeps the agent-server, the Vite-baked VITE_SESSION_API_KEY, and any |
| 630 | // `openhands-backends` localStorage entries the frontend has cached all |
| 631 | // pointing at the same value across dev restarts. |
| 632 | // |
| 633 | // LOCAL_BACKEND_API_KEY is the single user-facing env var for the API key. |
| 634 | // OH_SESSION_API_KEY_PATH overrides the persisted file path (used by tests). |
| 635 | const persistedKeyPath = env.OH_SESSION_API_KEY_PATH || DEFAULT_API_KEY_PATH; |
| 636 | const sessionApiKey = |
| 637 | env.LOCAL_BACKEND_API_KEY || |
| 638 | getOrCreatePersistedApiKeyFile(persistedKeyPath); |
| 639 | |
| 640 | // Host directory containing the legacy canvas_ui Python module. Persisted |
| 641 | // conversations created before the client_tools migration still reference |
| 642 | // its module qualname, so the agent-server can import it when resuming them. |
| 643 | const canvasToolsDir = fileURLToPath(new URL("../tools", import.meta.url)); |
| 644 | |
| 645 | return { |
| 646 | cwd, |
| 647 | backendPort, |
| 648 | vscodePort, |
| 649 | stateDir, |
| 650 | // tmux socket directory. Defaults to <stateDir>/tmux (under |
| 651 | // ~/.openhands/agent-canvas), matching where the rest of dev state lives |
| 652 | // and persisting across restarts. |
| 653 | // |
| 654 | // Do NOT use os.tmpdir() here: on macOS it resolves to the per-user |
| 655 | // $TMPDIR (/var/folders/.../T), which the OS periodically reaps |
| 656 | // (com.apple.bsd.dirhelper deletes entries untouched for a few days). |
| 657 | // Reaping deletes the live tmux socket while the server process keeps |
| 658 | // running, orphaning it — every later new-window then fails with |
| 659 | // "error connecting to .../openhands (No such file or directory)". |
| 660 | // |
| 661 | // The only hosts where <stateDir>/tmux can't hold the socket are those |
| 662 | // whose $HOME is a network/overlay mount without Unix-domain-socket |
| 663 | // support (some devcontainers, NFS/CIFS homes). Those rare cases can point |
| 664 | // tmux at a local, socket-capable path with the standard TMUX_TMPDIR env |
| 665 | // var (e.g. TMUX_TMPDIR=/tmp), which we honor and pass through below. |
| 666 | tmuxTmpDir: env.TMUX_TMPDIR || path.join(stateDir, "tmux"), |
| 667 | conversationsPath, |
| 668 | workspacesPath, |
no test coverage detected