From a5523cd1d9d51b49dde53f8d516333f21d4aed03 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 1 Apr 2026 02:14:38 +0000 Subject: [PATCH 1/3] fix: auto-register occode:// protocol handler on Linux, add to Windows installer - Windows (code.iss): add 4 [Registry] entries for Software\Classes\occode so Chrome can open occode:// links immediately after install, before first launch. Entries are removed on uninstall via uninsdeletekey. - Linux (extension.ts): add ensureLinuxProtocolHandler() called from activate(). Writes ~/.local/share/applications/occ-url-handler.desktop and updates ~/.config/mimeapps.list for both dev and production builds. Dev mode uses launch-editor.sh as Exec path; production uses process.execPath. Idempotent (keyed on exec path), silent failure, re-registers on app update or repo move. Closes ticket-023-protocol-handler-registration Co-Authored-By: Claude Sonnet 4.6 --- .../prd.md | 40 +++++++++ apps/editor/build/win32/code.iss | 6 ++ .../extensions/openclaw/src/extension.ts | 87 +++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 .tickets/ticket-023-protocol-handler-registration/prd.md diff --git a/.tickets/ticket-023-protocol-handler-registration/prd.md b/.tickets/ticket-023-protocol-handler-registration/prd.md new file mode 100644 index 00000000..7dc7d620 --- /dev/null +++ b/.tickets/ticket-023-protocol-handler-registration/prd.md @@ -0,0 +1,40 @@ +# PRD: Fix `occode://` Protocol Handler Registration (All Platforms) + +## 2.1 Problem Statement + +When `occ.mba.sh` completes a user login, it redirects the browser to `occode://openclaw.home/auth?token=`. The OS must have the `occode://` scheme registered to hand this URL off to the app. Chrome fails to open the redirect on Windows production builds, and Linux dev mode never registers the handler at all. + +**Root cause by platform:** +- **Windows (production):** The Inno Setup installer (`code.iss`) has zero `[Registry]` entries for `Software\Classes\occode`. The protocol is only registered at runtime via `app.setAsDefaultProtocolClient()`, meaning Chrome can't open `occode://` links unless the user has already launched the app first. +- **macOS:** Already handled — `darwinBundleURLTypes` in `electron.ts` bakes the scheme into `Info.plist` at build time. No fix needed. +- **Linux production (.deb):** Already handled — `postinst.template` installs the `.desktop` file and runs `update-desktop-database`. No fix needed. +- **Linux (dev + production):** The `.desktop` file is never registered when running via `launch-editor.sh` (dev) or when the `.deb` postinst is skipped/unavailable (production). Requires manual workaround. + +## 2.2 Proposed Solution + +1. **Windows:** Add `[Registry]` entries for `Software\Classes\occode` in `code.iss` so the URL scheme is registered at install time, before the user ever launches the app. +2. **Linux (dev + production):** Add `ensureLinuxProtocolHandler()` to `extension.ts` `activate()` to auto-write the `.desktop` file and update `mimeapps.list` on every launch where the handler isn't already current. + +## 2.3 Acceptance Criteria + +- [x] Windows: `occode://` links open the app from Chrome immediately after install, even without a prior launch +- [x] Windows: Protocol registry entries are cleaned up on uninstall (`uninsdeletekey` flag) +- [x] Linux: `~/.local/share/applications/occ-url-handler.desktop` written automatically on first launch (dev and production) +- [x] Linux: `~/.config/mimeapps.list` updated with `x-scheme-handler/occode` in both sections +- [x] Linux: Dev mode uses `launch-editor.sh` as Exec path; production uses `process.execPath` +- [x] Linux: Registration is idempotent — skips if already registered for the current executable path +- [x] Linux: Silent failure — never surfaces errors to the user +- [x] Linux: Re-registers automatically if the app is moved or updated (execPath changes) +- [x] macOS: No regression (no changes needed) + +## 2.4 Technical Considerations + +- Windows registry entries use `{#SoftwareClassesRootKey}` to respect user vs. machine install target +- Linux exec path: `launch-editor.sh` in dev (VSCODE_DEV=1 + file exists), otherwise `process.execPath` +- Idempotency keyed on exec path so re-registration triggers on app update or repo move +- `update-desktop-database` called fire-and-forget — silently ignored if not on PATH + +## 2.5 Files Changed + +- `apps/editor/build/win32/code.iss` — add 4 registry entries before `[Code]` section +- `apps/editor/extensions/openclaw/src/extension.ts` — add `ensureLinuxProtocolHandler()` + call site in `activate()` diff --git a/apps/editor/build/win32/code.iss b/apps/editor/build/win32/code.iss index 82db83ab..dfe29e91 100644 --- a/apps/editor/build/win32/code.iss +++ b/apps/editor/build/win32/code.iss @@ -1297,6 +1297,12 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Drive\shell\{#RegValu Root: {#EnvironmentRootKey}; Subkey: "{#EnvironmentKey}"; ValueType: expandsz; ValueName: "Path"; ValueData: "{code:AddToPath|{app}\bin}"; Tasks: addtopath; Check: NeedsAddToPath(ExpandConstant('{app}\bin')) +; occode:// URL protocol handler — registered at install time so Chrome can redirect without requiring a prior app launch +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\occode"; ValueType: string; ValueName: ""; ValueData: "URL:OCcode Protocol"; Flags: uninsdeletekey +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\occode"; ValueType: string; ValueName: "URL Protocol"; ValueData: ""; Flags: uninsdeletekey +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\occode\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#ExeBasename}.exe,0"; Flags: uninsdeletekey +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\occode\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""--open-url"" ""%1"""; Flags: uninsdeletekey + [Code] function IsBackgroundUpdate(): Boolean; begin diff --git a/apps/editor/extensions/openclaw/src/extension.ts b/apps/editor/extensions/openclaw/src/extension.ts index 2c6c915d..3d2a6fd9 100644 --- a/apps/editor/extensions/openclaw/src/extension.ts +++ b/apps/editor/extensions/openclaw/src/extension.ts @@ -599,6 +599,90 @@ function initBalanceBar(context: vscode.ExtensionContext): (amount?: number) => return () => {}; // spend is a no-op — kept so call sites don't break } +async function ensureLinuxProtocolHandler(context: vscode.ExtensionContext): Promise { + try { + if (process.platform !== 'linux') return; + + const HANDLER_KEY = 'occ.linuxProtocolHandlerRegisteredFor'; + const appRoot = vscode.env.appRoot; + + // Dev mode: launch via shell script so the full dev environment is set up correctly. + // Production: invoke the binary directly. + const devLauncher = path.resolve(appRoot, '../../launch-editor.sh'); + const execPath = process.env['VSCODE_DEV'] && fs.existsSync(devLauncher) + ? devLauncher + : process.execPath; + + const stored = context.globalState.get(HANDLER_KEY, ''); + if (stored === execPath) return; // already registered for this executable + + const product = JSON.parse( + fs.readFileSync(path.join(appRoot, 'product.json'), 'utf-8') + ) as { applicationName: string; urlProtocol: string; nameLong: string; linuxIconName: string; linuxDescription: string }; + + // 1. Write ~/.local/share/applications/occ-url-handler.desktop + const appsDir = path.join(os.homedir(), '.local', 'share', 'applications'); + fs.mkdirSync(appsDir, { recursive: true }); + + const desktopContent = [ + '[Desktop Entry]', + `Name=${product.nameLong} - URL Handler`, + `Comment=${product.linuxDescription}`, + 'GenericName=Text Editor', + `Exec="${execPath}" --open-url %U`, + `Icon=${product.linuxIconName}`, + 'Type=Application', + 'NoDisplay=true', + 'StartupNotify=true', + 'Categories=Utility;TextEditor;Development;IDE;', + `MimeType=x-scheme-handler/${product.urlProtocol};`, + 'Keywords=vscode;', + '', + ].join('\n'); + + fs.writeFileSync( + path.join(appsDir, `${product.applicationName}-url-handler.desktop`), + desktopContent, 'utf-8' + ); + + // 2. Update ~/.config/mimeapps.list + const mimeappsPath = path.join(os.homedir(), '.config', 'mimeapps.list'); + const mimeEntry = `x-scheme-handler/${product.urlProtocol}=${product.applicationName}-url-handler.desktop`; + + let mimeContent = ''; + try { mimeContent = fs.readFileSync(mimeappsPath, 'utf-8'); } catch { /* new file */ } + + function ensureMimeSection(content: string, section: string, entry: string): string { + const header = `[${section}]`; + const keyPrefix = entry.split('=')[0] + '='; + if (!content.includes(header)) { + return content + (content.endsWith('\n') || content === '' ? '' : '\n') + `${header}\n${entry}\n`; + } + const lines = content.split('\n'); + const secIdx = lines.findIndex(l => l.trim() === header); + for (let i = secIdx + 1; i < lines.length; i++) { + if (lines[i].startsWith('[')) { lines.splice(secIdx + 1, 0, entry); return lines.join('\n'); } + if (lines[i].startsWith(keyPrefix)) { lines[i] = entry; return lines.join('\n'); } + } + lines.splice(secIdx + 1, 0, entry); + return lines.join('\n'); + } + + mimeContent = ensureMimeSection(mimeContent, 'Default Applications', mimeEntry); + mimeContent = ensureMimeSection(mimeContent, 'Added Associations', mimeEntry); + fs.mkdirSync(path.dirname(mimeappsPath), { recursive: true }); + fs.writeFileSync(mimeappsPath, mimeContent, 'utf-8'); + + // 3. Refresh desktop database (fire-and-forget, silently ignored if not available) + cp.spawn('update-desktop-database', [appsDir], { stdio: 'ignore', detached: true }).unref(); + + // 4. Persist so we skip on next launch + await context.globalState.update(HANDLER_KEY, execPath); + } catch { + // Silent failure — non-critical + } +} + export async function activate(context: vscode.ExtensionContext): Promise { // ── One-time migration: move JWT from globalState → SecretStorage ──────────── const legacyJwt = context.globalState.get(OCC_JWT_KEY, ''); @@ -607,6 +691,9 @@ export async function activate(context: vscode.ExtensionContext): Promise Date: Wed, 1 Apr 2026 02:23:45 +0000 Subject: [PATCH 2/3] fix: register missing openclaw.host.setup.{local,docker,ssh} commands These three commands were called by routeHome() and the HomePanel host picker but never registered, causing silent failures on every startup where a local install or Docker container was detected. Also broke openclaw.install from the command palette (it delegates to openclaw.host.setup.local). Each command sets the WindowHostBinding for the selected host type (if not already set) then calls HomePanel.createOrShow(), which renders the correct dashboard or setup wizard based on the binding. Closes ticket-024-fix-extension-routing-commands Co-Authored-By: Claude Sonnet 4.6 --- .../prd.md | 43 +++++++++++++++++++ .../extensions/openclaw/src/extension.ts | 25 +++++++++++ 2 files changed, 68 insertions(+) create mode 100644 .tickets/ticket-024-fix-extension-routing-commands/prd.md diff --git a/.tickets/ticket-024-fix-extension-routing-commands/prd.md b/.tickets/ticket-024-fix-extension-routing-commands/prd.md new file mode 100644 index 00000000..361529bd --- /dev/null +++ b/.tickets/ticket-024-fix-extension-routing-commands/prd.md @@ -0,0 +1,43 @@ +# PRD: Fix Extension Panel Routing — Ghost Commands + +## 2.1 Problem Statement + +Three commands are called throughout the extension but were never registered, causing silent failures in all panel routing: + +- `openclaw.host.setup.local` — called by `routeHome()` and the host picker in `HomePanel` +- `openclaw.host.setup.docker` — called by `routeHome()` and the host picker in `HomePanel` +- `openclaw.host.setup.ssh` — called by the host picker in `HomePanel` + +**Impact:** +- `routeHome()` (the `openclaw.home` command) silently fails whenever a local install or Docker container is detected — the home panel never opens +- The `openclaw.install` command (visible in the command palette) delegates to `openclaw.host.setup.local` and therefore does nothing +- Picking a host type in the host picker closes the picker but opens no follow-up panel +- All auto-routing on startup is broken for any configured machine + +## 2.2 Root Cause + +The extension was refactored to a multi-host adapter system (`HostRegistry`/`HostManager`) but the three host setup command registrations were deleted and never replaced. The call sites remained. + +## 2.3 Solution + +Register the three missing commands in `extension.ts` `activate()` alongside the existing `openclaw.home` and `openclaw.home.picker` commands: + +- `openclaw.host.setup.local` — sets `WindowHostBinding` to `{ type: 'local', hostId: 'local:main', port: configuredPort, label: 'Local' }` if not already set, then calls `HomePanel.createOrShow()` +- `openclaw.host.setup.docker` — sets `WindowHostBinding` to `{ type: 'docker', hostId: 'docker:occ-openclaw', port: 18789, label: 'Docker' }` if not already set, then calls `HomePanel.createOrShow()` +- `openclaw.host.setup.ssh` — opens `HomePanel.createOrShow()` directly (SSH config is entered via the panel UI) + +Binding is only written if the type doesn't already match, preserving any existing port/hostId configured for that host. + +## 2.4 Acceptance Criteria + +- [x] `openclaw.home` command opens the home panel when local OpenClaw is installed +- [x] `openclaw.home` command opens the home panel when Docker container is running +- [x] `openclaw.install` from the command palette opens the local setup panel +- [x] Picking "local" in the host picker opens the local dashboard +- [x] Picking "docker" in the host picker opens the docker dashboard +- [x] Picking "ssh" in the host picker opens the home panel for SSH setup +- [x] Auto-routing on startup works correctly for all configured machines + +## 2.5 Files Changed + +- `apps/editor/extensions/openclaw/src/extension.ts` — register 3 missing commands after `openclaw.home.picker` (~line 763) diff --git a/apps/editor/extensions/openclaw/src/extension.ts b/apps/editor/extensions/openclaw/src/extension.ts index 3d2a6fd9..319f778b 100644 --- a/apps/editor/extensions/openclaw/src/extension.ts +++ b/apps/editor/extensions/openclaw/src/extension.ts @@ -763,6 +763,31 @@ export async function activate(context: vscode.ExtensionContext): Promise { routeHome(context.extensionUri, context, true); }), + // Host-specific setup commands — invoked by routeHome() and the host picker in HomePanel. + // Each sets the window binding for this host type then opens the home panel, + // which renders the appropriate dashboard or setup wizard based on the binding. + vscode.commands.registerCommand('openclaw.host.setup.local', async () => { + const existing = context.workspaceState.get(WINDOW_HOST_KEY); + if (!existing || existing.type !== 'local') { + await context.workspaceState.update(WINDOW_HOST_KEY, { + type: 'local', hostId: 'local:main', port: getConfiguredGatewayPort(), label: 'Local', + } satisfies WindowHostBinding); + } + HomePanel.createOrShow(context.extensionUri); + }), + vscode.commands.registerCommand('openclaw.host.setup.docker', async () => { + const existing = context.workspaceState.get(WINDOW_HOST_KEY); + if (!existing || existing.type !== 'docker') { + await context.workspaceState.update(WINDOW_HOST_KEY, { + type: 'docker', hostId: 'docker:occ-openclaw', port: DEFAULT_GATEWAY_PORT, label: 'Docker', + } satisfies WindowHostBinding); + } + HomePanel.createOrShow(context.extensionUri); + }), + vscode.commands.registerCommand('openclaw.host.setup.ssh', () => { + // SSH host details are entered via the home panel UI — open it and let the panel drive. + HomePanel.createOrShow(context.extensionUri); + }), vscode.commands.registerCommand('openclaw.configure', async () => { const windowHostBinding = context.workspaceState.get(WINDOW_HOST_KEY); From dd58329a1d69ee87bf5e4887702cac1ce687413a Mon Sep 17 00:00:00 2001 From: linuxdev Date: Wed, 1 Apr 2026 03:13:40 -0400 Subject: [PATCH 3/3] chore(release): 3.2.47 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- version.txt | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9e096f6..ec6e0061 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. +## [3.2.47](https://github.com/damoahdominic/occ/compare/v3.2.46...v3.2.47) (2026-04-01) + + +### Bug Fixes + +* auto-register occode:// protocol handler on Linux, add to Windows installer ([a5523cd](https://github.com/damoahdominic/occ/commit/a5523cd1d9d51b49dde53f8d516333f21d4aed03)) +* register missing openclaw.host.setup.{local,docker,ssh} commands ([bfe334e](https://github.com/damoahdominic/occ/commit/bfe334e1abf87fb98853d513eb03c3698f439d5f)) + ## [3.2.46](https://github.com/damoahdominic/occ/compare/v3.2.45...v3.2.46) (2026-03-31) diff --git a/package.json b/package.json index 524c0a62..2cde78e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "occode", - "version": "3.2.46", + "version": "3.2.47", "private": true, "description": "OCcode — branded cross-platform IDE wrapper with OpenClaw extension", "workspaces": [ diff --git a/version.txt b/version.txt index 96395c56..494bf99a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.2.46 \ No newline at end of file +3.2.47 \ No newline at end of file