fix: repair desktop launcher local view

This commit is contained in:
Simos Mikelatos
2026-06-19 14:20:23 +00:00
parent 4d70a2588c
commit f4c68942a5
7 changed files with 88 additions and 30 deletions

View File

@@ -16,6 +16,9 @@ on:
permissions:
contents: read
# This workflow publishes releases with write credentials, so actions are pinned
# to immutable commit SHAs. The trailing comments keep the original major tag
# visible for maintenance context.
jobs:
build-macos-semantic-helper:
strategy:
@@ -28,8 +31,10 @@ jobs:
target_dir: darwin-x64
runs-on: ${{ matrix.runs_on }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
- name: Build macOS semantic helper
@@ -42,7 +47,7 @@ jobs:
run: |
mkdir -p "semantic-helper-artifact/${{ matrix.target_dir }}"
cp "server/modules/computer-use/semantics/bin/${{ matrix.target_dir }}/CloudCLISemantics" "semantic-helper-artifact/${{ matrix.target_dir }}/"
- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: semantic-helper-${{ matrix.target_dir }}
path: semantic-helper-artifact/*
@@ -59,8 +64,10 @@ jobs:
target_dir: win32-arm64
runs-on: ${{ matrix.runs_on }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
- name: Build Windows semantic helper
@@ -75,7 +82,7 @@ jobs:
run: |
mkdir -p "semantic-helper-artifact/${{ matrix.target_dir }}"
cp "server/modules/computer-use/semantics/bin/${{ matrix.target_dir }}/CloudCLISemantics.exe" "semantic-helper-artifact/${{ matrix.target_dir }}/"
- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: semantic-helper-${{ matrix.target_dir }}
path: semantic-helper-artifact/*
@@ -90,12 +97,12 @@ jobs:
contents: write
id-token: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0
token: ${{ secrets.RELEASE_PAT }}
- uses: actions/setup-node@v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
registry-url: https://registry.npmjs.org
@@ -107,7 +114,7 @@ jobs:
- run: npm ci
- uses: actions/download-artifact@v6
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
with:
pattern: semantic-helper-*
path: server/modules/computer-use/semantics/bin

View File

@@ -52,6 +52,8 @@ export class ComputerAgentController {
this.connectedUrls = new Set();
this.currentTargets = [];
this.stdoutBuffer = '';
this.lastEvent = null;
this.lastError = null;
}
getSettings() {
@@ -65,6 +67,9 @@ export class ComputerAgentController {
running: Boolean(this.child),
connectedCount: this.connectedUrls.size,
targetCount: this.currentTargets.length,
targetUrls: [...this.currentTargets],
lastEvent: this.lastEvent,
lastError: this.lastError,
};
}
@@ -105,6 +110,7 @@ export class ComputerAgentController {
if (!this.settings.enabled || wsTargets.length === 0) {
this.stop();
this.currentTargets = [];
this.lastEvent = this.settings.enabled ? 'no-targets' : 'disabled';
return;
}
@@ -113,6 +119,8 @@ export class ComputerAgentController {
}
this.currentTargets = wsTargets;
this.lastEvent = 'restarting';
this.lastError = null;
this.restart(wsTargets);
}
@@ -140,6 +148,8 @@ export class ComputerAgentController {
this.child.once('error', (error) => {
console.error('[ComputerAgent] failed to start:', error.message);
this.lastEvent = 'start-error';
this.lastError = error.message;
this.child = null;
this.onChange?.();
});
@@ -147,12 +157,16 @@ export class ComputerAgentController {
this.child.stdout?.on('data', (chunk) => this.handleStdout(String(chunk)));
this.child.stderr?.on('data', (chunk) => {
for (const line of String(chunk).split(/\r?\n/)) {
if (line.trim()) console.error('[ComputerAgent]', line);
if (line.trim()) {
this.lastError = line.trim();
console.error('[ComputerAgent]', line);
}
}
});
this.child.once('exit', (code) => {
console.log(`[ComputerAgent] exited (code ${code ?? 'null'})`);
this.lastEvent = `exit:${code ?? 'null'}`;
this.child = null;
this.connectedUrls = new Set();
this.onChange?.();
@@ -185,10 +199,23 @@ export class ComputerAgentController {
switch (payload.type) {
case 'connected':
this.connectedUrls.add(payload.url);
this.lastEvent = 'connected';
this.lastError = null;
this.onChange?.();
break;
case 'disconnected':
this.connectedUrls.delete(payload.url);
this.lastEvent = 'disconnected';
this.onChange?.();
break;
case 'starting':
this.lastEvent = 'starting';
this.lastError = null;
this.onChange?.();
break;
case 'error':
this.lastEvent = 'error';
this.lastError = payload.message || 'Computer agent error.';
this.onChange?.();
break;
case 'consent-request': {

View File

@@ -181,19 +181,18 @@ window.__MOCK_STATE__ = {
function computerUseStatus(state) {
var computerUse = state && state.computerUse ? state.computerUse : {};
var connectedCount = computerUse.connectedCount || 0;
var environmentLabel = connectedCount + ' environment' + (connectedCount === 1 ? '' : 's');
if (!computerUse.enabled) {
return { label: 'Disabled', tone: 'idle', detail: 'CloudCLI cannot use this computer.' };
}
if (!computerUse.targetCount) {
return { label: 'Not linked', tone: 'warn', detail: 'No running cloud environment found for this account.' };
}
if (!computerUse.connectedCount) {
return { label: 'Connecting', tone: 'warn', detail: 'Trying to link to ' + computerUse.targetCount + ' running cloud environment' + (computerUse.targetCount === 1 ? '' : 's') + '.' };
if (!connectedCount) {
return { label: 'Not connected', tone: 'warn', detail: 'No environment connected.' };
}
if (computerUse.consentMode === 'auto') {
return { label: 'Linked', tone: 'warn', detail: 'Unattended access is on for ' + computerUse.connectedCount + ' cloud environment' + (computerUse.connectedCount === 1 ? '' : 's') + '.' };
return { label: 'Connected', tone: 'warn', detail: environmentLabel + ' connected. Unattended access is on.' };
}
return { label: 'Linked', tone: 'ok', detail: 'Approval prompts are ready for ' + computerUse.connectedCount + ' cloud environment' + (computerUse.connectedCount === 1 ? '' : 's') + '.' };
return { label: 'Connected', tone: 'ok', detail: environmentLabel + ' connected.' };
}
var CC = {
@@ -342,6 +341,11 @@ window.__MOCK_STATE__ = {
case 'set-theme-mode':
return CC.run('Saved', function () { return bridge.updateSetting('themeMode', node.value); });
case 'set-computer-mode':
CC.state.computerUse = {
...((CC.state && CC.state.computerUse) || {}),
enabled: true,
consentMode: node.value === 'auto' ? 'auto' : 'ask',
};
return CC.run('Saved', function () {
return bridge.updateComputerUse({
enabled: true,
@@ -349,6 +353,10 @@ window.__MOCK_STATE__ = {
});
});
case 'set-computer-enabled':
CC.state.computerUse = {
...((CC.state && CC.state.computerUse) || {}),
enabled: !!node.value,
};
return CC.run('Saved', function () {
var current = (CC.state && CC.state.computerUse) || { consentMode: 'ask' };
return bridge.updateComputerUse({
@@ -731,10 +739,11 @@ window.__MOCK_STATE__ = {
}
function localPane(state) {
var computerStatus = CC.computerUseStatus(state);
return '<div class="pane-h"><div><h2 class="pane-title">Local servers</h2><p class="pane-sub">Manage Local CloudCLI on this machine. No account required.</p></div></div>' +
'<div class="card"><div class="card-head"><div><div class="card-t">Local server</div><div class="card-sub mono">' + CC.esc(CC.localUrl(state) || 'Starts on demand') + '</div></div><div class="card-tools"><span class="dot" style="background:' + (state.localServerRunning ? 'var(--ok)' : 'var(--tx3)') + '"></span><button class="icon-btn" data-cc-action="local-settings-toggle" title="Local settings">' + CC.icon('gear', 16) + '</button></div></div>' +
'<div class="card-actions"><button class="btn pri" data-cc-action="local">' + CC.icon('play', 15) + 'Open Local CloudCLI</button><button class="btn" data-cc-action="open-web">' + CC.icon('arrow', 14) + 'Open in browser</button><button class="btn" data-cc-action="copy-web">' + CC.icon('copy', 14) + 'Copy URL</button></div></div>' +
'<div class="card"><div class="card-head"><div><div class="card-t">Computer Use</div><div class="card-sub">' + CC.esc(computerUseStatus(state).detail) + '</div></div><div class="card-tools"><span class="badge ' + CC.esc(computerUseStatus(state).tone) + '">' + CC.esc(computerUseStatus(state).label) + '</span><button class="icon-btn" data-cc-action="computer-settings-toggle" title="Computer Use settings">' + CC.icon('monitor', 16) + '</button></div></div>' +
'<div class="card"><div class="card-head"><div><div class="card-t">Computer Use</div><div class="card-sub">' + CC.esc(computerStatus.detail) + '</div></div><div class="card-tools"><span class="badge ' + CC.esc(computerStatus.tone) + '">' + CC.esc(computerStatus.label) + '</span><button class="icon-btn" data-cc-action="computer-settings-toggle" title="Computer Use settings">' + CC.icon('monitor', 16) + '</button></div></div>' +
'<div class="card-actions"><button class="btn" data-cc-action="refresh-environments">' + CC.icon('refresh', 14) + 'Refresh / relink</button><button class="btn" data-cc-action="computer-settings-toggle">' + CC.icon('settings', 14) + 'Open settings</button></div></div>';
}

View File

@@ -278,6 +278,11 @@ function getDiagnosticsText() {
cloudConnected: Boolean(cloudAccount?.apiKey),
cloudEmail: cloudAccount?.email || null,
cloudEnvironmentCount: cloud.getEnvironments().length,
cloudRunningEnvironmentCount: getRunningEnvironmentUrls().length,
cloudAuthState: cloud.getAuthState(),
computerUse: computerAgent?.getState() || null,
computerUseSettingsPath: getComputerUseSettingsPath(),
cloudAccountPath: getStorePath(),
controlPlaneUrl: CLOUDCLI_CONTROL_PLANE_URL,
}, null, 2);
}

View File

@@ -24,6 +24,14 @@ function readString(value: unknown): string {
return typeof value === 'string' ? value.trim() : '';
}
function requireApp(input: Record<string, unknown>): string {
const app = readString(input.app);
if (!app) {
throw new Error('app is required.');
}
return app;
}
function readNumber(value: unknown): number | undefined {
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
}
@@ -102,7 +110,7 @@ function getHelperAdapter(): SemanticAdapter | null {
function shouldFallbackFromHelper(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error);
return /not implemented|unavailable|not found|does not exist/i.test(message);
return /not implemented|unavailable|not found|does not exist|timed out|not running|exited with code|failed to start/i.test(message);
}
async function withHelperState(
@@ -347,7 +355,7 @@ export const computerSemanticsService = {
return getAppState(sessionId, readString(input.app));
case 'click':
case 'click_element': {
const app = readString(input.app);
const app = requireApp(input);
const helperState = await withHelperState(sessionId, (adapter) => adapter.clickElement({ ...input, sessionId, app }));
if (helperState) {
return helperState;
@@ -366,7 +374,7 @@ export const computerSemanticsService = {
return getAppState(sessionId, app);
}
case 'drag': {
const app = readString(input.app);
const app = requireApp(input);
const helperState = await withHelperState(sessionId, (adapter) => adapter.drag({ ...input, sessionId, app }));
if (helperState) {
return helperState;
@@ -384,7 +392,7 @@ export const computerSemanticsService = {
}
case 'scroll':
case 'scroll_element': {
const app = readString(input.app);
const app = requireApp(input);
const helperState = await withHelperState(sessionId, (adapter) => adapter.scrollElement({ ...input, sessionId, app }));
if (helperState) {
return helperState;
@@ -398,7 +406,7 @@ export const computerSemanticsService = {
return getAppState(sessionId, app);
}
case 'type_text': {
const app = readString(input.app);
const app = requireApp(input);
const helperState = await withHelperState(sessionId, (adapter) => adapter.typeText({ ...input, sessionId, app }));
if (helperState) {
return helperState;
@@ -407,7 +415,7 @@ export const computerSemanticsService = {
return getAppState(sessionId, app);
}
case 'press_key': {
const app = readString(input.app);
const app = requireApp(input);
const helperState = await withHelperState(sessionId, (adapter) => adapter.pressKey({ ...input, sessionId, app }));
if (helperState) {
return helperState;
@@ -416,7 +424,7 @@ export const computerSemanticsService = {
return getAppState(sessionId, app);
}
case 'set_value': {
const app = readString(input.app);
const app = requireApp(input);
const helperState = await withHelperState(sessionId, (adapter) => adapter.setValue({ ...input, sessionId, app }));
if (helperState) {
return helperState;
@@ -432,7 +440,7 @@ export const computerSemanticsService = {
return getAppState(sessionId, app);
}
case 'perform_secondary_action': {
const app = readString(input.app);
const app = requireApp(input);
const helperState = await withHelperState(sessionId, (adapter) => adapter.performSecondaryAction({ ...input, sessionId, app }));
if (helperState) {
return helperState;

View File

@@ -74,8 +74,10 @@ func bounds(_ element: AXUIElement) -> [String: Double]? {
var point = CGPoint.zero
var size = CGSize.zero
guard AXValueGetValue(positionValue as! AXValue, .cgPoint, &point),
AXValueGetValue(sizeValue as! AXValue, .cgSize, &size)
guard let positionAxValue = positionValue as? AXValue,
let sizeAxValue = sizeValue as? AXValue,
AXValueGetValue(positionAxValue, .cgPoint, &point),
AXValueGetValue(sizeAxValue, .cgSize, &size)
else { return nil }
return [

View File

@@ -29,11 +29,11 @@ function getTabTitle(activeTab: AppTab, shouldShowTasksTab: boolean, t: (key: st
}
if (activeTab === 'browser') {
return 'Browser';
return t('tabs.browser');
}
if (activeTab === 'computer') {
return 'Computer Use';
return t('tabs.computer');
}
return 'Project';