diff --git a/README.md b/README.md
index 030a8ad6..1020105c 100644
--- a/README.md
+++ b/README.md
@@ -155,18 +155,3 @@ Made with [contrib.rocks](https://contrib.rocks).
-## Sponsors
-
-OpenClaw Code is made possible by the generous support of our sponsors.
-
-π Diamond Sponsor
-
-
-
-
-
-
-
----
-
-Want to sponsor OpenClaw Code and support open-source AI? Reach out at [team@mba.sh](mailto:team@mba.sh).
diff --git a/apps/editor/extensions/openclaw/src/extension.ts b/apps/editor/extensions/openclaw/src/extension.ts
index ce4ba52d..5bb7adc7 100644
--- a/apps/editor/extensions/openclaw/src/extension.ts
+++ b/apps/editor/extensions/openclaw/src/extension.ts
@@ -140,7 +140,11 @@ async function openOpenClawFolder(): Promise {
// Only open the workspace if OpenClaw is already installed.
const openclawPath = path.join(os.homedir(), '.openclaw');
if (!fs.existsSync(openclawPath)) {
- return; // OpenClaw not installed yet β nothing to open.
+ // OpenClaw not installed β clean up any stale workspace file so VS Code
+ // doesn't keep opening the workspace with a missing folder on next restart.
+ const staleWorkspaceFile = path.join(occPath, WORKSPACE_FILENAME);
+ try { if (fs.existsSync(staleWorkspaceFile)) { fs.unlinkSync(staleWorkspaceFile); } } catch { /* non-fatal */ }
+ return;
}
// Workspace file lives in ~/.occ, points at ~/.openclaw as the folder.
diff --git a/apps/editor/extensions/openclaw/src/panels/home.ts b/apps/editor/extensions/openclaw/src/panels/home.ts
index 3b451d92..bcad8849 100644
--- a/apps/editor/extensions/openclaw/src/panels/home.ts
+++ b/apps/editor/extensions/openclaw/src/panels/home.ts
@@ -8,6 +8,20 @@ import * as path from 'path';
type GatewayStatus = 'checking' | 'running' | 'stopped' | 'starting' | 'stopping' | 'restarting' | 'errored' | 'ai-fixing';
+// ββ OCC Legacy model constants ββββββββββββββββββββββββββββββββββββββββββββββββ
+const OCC_LEGACY_MODEL_ID = 'occ-legacy';
+const OCC_LEGACY_MODEL_NAME = 'occ-legacy';
+const OCC_LEGACY_BASE_URL = 'https://occ.mba.sh/v1';
+const OCC_LEGACY_API = 'openai-completions';
+const OCC_LEGACY_COST = {
+ input: 0.0000006,
+ output: 0.000003,
+ cacheRead: 0.0000001,
+ cacheWrite: 0,
+};
+const OCC_LEGACY_CONTEXT_WINDOW = 262144;
+const OCC_LEGACY_MAX_TOKENS = 262144;
+
/**
* Resolves the directory where OpenClaw stores its workspace files
* (AGENTS.md, IDENTITY.md, USER.md, TOOLS.md, MEMORY.md, SOUL.md, HEARTBEAT.md).
@@ -251,7 +265,7 @@ export class HomePanel {
post({ type: 'installState', state: 'running' });
const env = panel._buildExecEnv();
- const isPermError = (s: string) => /EACCES|permission denied|EPERM|not permitted/i.test(s);
+ const isPermError = (s: string) => /EACCES|permission denied|EPERM|not permitted|Need sudo access|needs to be an Administrator/i.test(s);
// Spawn a command silently and stream output to the panel.
const runCaptured = (cmd: string, args: string[], opts: cp.SpawnOptions = {}): Promise<{ code: number }> =>
@@ -349,11 +363,79 @@ export class HomePanel {
}
}
tee('\nnpm install did not succeed β trying full installer script...\n');
+ } else if (platform !== 'win32') {
+
+ // ββ Unix: npm not found β silent Node.js install (no terminal) ββββββββββ
+ // Step A: try nvm (no sudo, no password needed)
+ const nvmSh = path.join(os.homedir(), '.nvm', 'nvm.sh');
+ if (fs.existsSync(nvmSh)) {
+ tee('nvm detected β installing Node.js LTS...\n');
+ const nvmR = await runCaptured('bash', ['-c',
+ `. "${nvmSh}" && nvm install --lts && nvm use --lts && npm install -g openclaw`
+ ]);
+ if (nvmR.code === 0) { await fixOpenclawPermissions(); await succeed(); return; }
+ tee('nvm install failed β falling back to system install...\n');
+ }
+
+ // Step B: collect password once, cache sudo for all subsequent steps
+ tee('\nNode.js is required. Your password is needed once to install it.\n');
+ const sudoOk = await cacheSudo('Enter your password to install Node.js');
+ if (!sudoOk) { tee('Incorrect password or cancelled.\n'); failCancelled(); return; }
+
+ if (platform === 'darwin') {
+ // macOS: download official Node.js universal pkg, install silently with cached sudo
+ const nodeVersion = '20.18.2';
+ const pkgUrl = `https://nodejs.org/dist/v${nodeVersion}/node-v${nodeVersion}.pkg`;
+ const pkgPath = `/tmp/.occ-node-${nodeVersion}.pkg`;
+ tee(`Downloading Node.js v${nodeVersion}...\n`);
+ const dlR = await runCaptured('curl', ['-fsSL', pkgUrl, '-o', pkgPath]);
+ if (dlR.code !== 0) { try { fs.unlinkSync(pkgPath); } catch {} await fail(); return; }
+ tee('Installing Node.js (this may take a moment)...\n');
+ const instR = await runCaptured('sudo', ['-n', 'installer', '-pkg', pkgPath, '-target', '/']);
+ try { fs.unlinkSync(pkgPath); } catch { /* non-fatal */ }
+ if (instR.code !== 0) { await fail(); return; }
+ } else {
+ // Linux: detect package manager and install Node.js LTS via nodesource
+ const hasCmdSync = (cmd: string): boolean => {
+ try { cp.execSync(`which ${cmd}`, { env, stdio: 'ignore' }); return true; } catch { return false; }
+ };
+ tee('Installing Node.js via package manager...\n');
+ let pkgResult: { code: number } | undefined;
+ if (hasCmdSync('apt-get')) {
+ pkgResult = await runCaptured('sudo', ['-n', 'bash', '-c',
+ 'curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && apt-get install -y nodejs'
+ ]);
+ } else if (hasCmdSync('dnf')) {
+ pkgResult = await runCaptured('sudo', ['-n', 'bash', '-c',
+ 'curl -fsSL https://rpm.nodesource.com/setup_lts.x | bash - && dnf install -y nodejs'
+ ]);
+ } else if (hasCmdSync('yum')) {
+ pkgResult = await runCaptured('sudo', ['-n', 'bash', '-c',
+ 'curl -fsSL https://rpm.nodesource.com/setup_lts.x | bash - && yum install -y nodejs'
+ ]);
+ }
+ if (!pkgResult) { tee('No supported package manager found (tried apt-get, dnf, yum).\n'); await fail(); return; }
+ if (pkgResult.code !== 0) { await fail(); return; }
+ }
+
+ // Step C: npm is now installed β find it and install openclaw
+ tee('Installing OpenClaw...\n');
+ const npmCandidates = ['/usr/local/bin/npm', '/usr/bin/npm'];
+ const npmBin = npmCandidates.find(p => fs.existsSync(p)) ?? 'npm';
+ const npmR1 = await runCaptured(npmBin, ['install', '-g', 'openclaw']);
+ if (npmR1.code === 0) { await fixOpenclawPermissions(); await succeed(); return; }
+ // Global prefix dir may be root-owned β retry with cached sudo
+ if (isPermError(fullLog)) {
+ const npmR2 = await runCaptured('sudo', ['-n', npmBin, 'install', '-g', 'openclaw']);
+ if (npmR2.code === 0) { await fixOpenclawPermissions(); await succeed(); return; }
+ }
+ await fail(); return;
+
} else {
tee('npm not found β running full installer script...\n');
}
- // ββ Step 2: full install script, captured (no terminal) βββββββββββββββββββ
+ // ββ Step 2: full install script ββ (npm found but failed, or Windows no npm)
if (platform === 'win32') {
tee('Running PowerShell installer...\n');
const psArgs = [
@@ -364,13 +446,13 @@ export class HomePanel {
const r = await runCaptured('powershell', psArgs, { windowsHide: true } as cp.SpawnOptions);
if (r.code === 0) { await succeed(); return; }
} else {
+ // npm was found but install failed β try the openclaw installer script
tee('Running install script...\n');
const r1 = await runCaptured('bash', ['-c', 'curl -fsSL https://openclaw.ai/install.sh | bash']);
if (r1.code === 0) {
await fixOpenclawPermissions();
await succeed(); return;
}
- // Permission error β ask for sudo, run installer under sudo, then fix permissions
if (isPermError(fullLog)) {
tee('\nPermission error in installer β elevated access required.\n');
const ok = await cacheSudo('Enter your system password to complete installation');
@@ -932,6 +1014,37 @@ export class HomePanel {
JSON.stringify({ tier: 'free', grantedAt: new Date().toISOString(), limitUsd: 1.00 }),
);
} catch { /* non-fatal */ }
+ // Patch openclaw.json to inject correct cost/context metadata for occ-legacy.
+ // openclaw onboard writes the model with null/zero values; we fix them here.
+ try {
+ const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
+ const raw = fs.readFileSync(configPath, 'utf-8');
+ const cfg = JSON.parse(raw) as Record;
+ // Recursively find any model object with id === OCC_LEGACY_MODEL_ID and patch it.
+ const patchModel = (obj: unknown): boolean => {
+ if (!obj || typeof obj !== 'object') return false;
+ if (Array.isArray(obj)) {
+ for (const item of obj) {
+ if (patchModel(item)) return true;
+ }
+ return false;
+ }
+ const o = obj as Record;
+ if (o['id'] === OCC_LEGACY_MODEL_ID) {
+ o['name'] = OCC_LEGACY_MODEL_NAME;
+ o['reasoning'] = false;
+ o['input'] = ['text'];
+ o['cost'] = { ...OCC_LEGACY_COST };
+ o['contextWindow'] = OCC_LEGACY_CONTEXT_WINDOW;
+ o['maxTokens'] = OCC_LEGACY_MAX_TOKENS;
+ return true;
+ }
+ for (const v of Object.values(o)) { patchModel(v); }
+ return false;
+ };
+ patchModel(cfg);
+ fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2));
+ } catch { /* non-fatal β openclaw.json may not exist yet */ }
}
setTimeout(() => {
HomePanel.refresh();
@@ -994,13 +1107,9 @@ export class HomePanel {
private async _runUninstall(): Promise {
const post = (msg: object) => { try { this._panel.webview.postMessage(msg); } catch {} };
- const home = os.homedir();
post({ type: 'uninstallLog', text: 'Handing off to AI for uninstallβ¦\n', done: true, ok: true });
- // IMPORTANT: workspace file deletion must happen AFTER MoltPilot is activated β
- // deleting the open .code-workspace file can trigger a VS Code window reload
- // which would cancel any pending setTimeout callbacks.
setTimeout(() => {
post({ type: 'uninstallDone' });
vscode.commands.executeCommand(
@@ -1009,31 +1118,10 @@ export class HomePanel {
'agent',
);
this._schedulePostUninstallClose();
-
- // Remove ~/.openclaw from the VS Code workspace Explorer sidebar.
- // Done here (after MoltPilot fires) so workspace changes don't trigger
- // a window reload that would cancel this callback before it executes.
- try {
- const openclawUri = vscode.Uri.file(path.join(home, '.openclaw'));
- const folders = vscode.workspace.workspaceFolders ?? [];
- const idx = folders.findIndex(f => f.uri.fsPath === openclawUri.fsPath);
- if (idx !== -1) {
- vscode.workspace.updateWorkspaceFolders(idx, 1);
- }
- } catch { /* non-fatal */ }
-
- // Delete the .code-workspace file so the folder doesn't come back on next launch.
- // Use a small extra delay so VS Code settles after the updateWorkspaceFolders call
- // before we remove the workspace file (avoids "workspace file missing" prompts).
- setTimeout(() => {
- try {
- const wsFile = path.join(home, '.occ', 'My OpenClaw Workspace.code-workspace');
- if (fs.existsSync(wsFile)) { fs.unlinkSync(wsFile); }
- } catch { /* non-fatal */ }
- }, 800);
-
- // Reload the panel so it shows the "not installed" state
- setTimeout(() => HomePanel.refresh(), 1200);
+ // Workspace cleanup (removing ~/.openclaw folder and .code-workspace file) is handled
+ // by openOpenClawFolder() on the next startup β manipulating workspace state here
+ // triggers VS Code to modify/reload the workspace, which can cause the extension to
+ // re-activate and open a duplicate OCC Home tab.
}, 1200);
}
@@ -2321,6 +2409,7 @@ The binary is already downloaded β do NOT re-download or compile anything.`;
border: 1px solid #2b2b2b;
border-radius: 8px;
padding: 6px clamp(10px, 3vw, 16px);
+ margin-top: 20px;
margin-bottom: 10px;
font-size: clamp(11px, 2.5vw, 12px);
}
@@ -2983,7 +3072,8 @@ The binary is already downloaded β do NOT re-download or compile anything.`;
${isInstalled ? `