diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dffa10f7..32ace264 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/electron/computerAgent.js b/electron/computerAgent.js index d832ca84..b28cea6e 100644 --- a/electron/computerAgent.js +++ b/electron/computerAgent.js @@ -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': { diff --git a/electron/launcher/launcher.js b/electron/launcher/launcher.js index f4b02d87..11bf6226 100644 --- a/electron/launcher/launcher.js +++ b/electron/launcher/launcher.js @@ -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 '
Manage Local CloudCLI on this machine. No account required.