Skip to content

feat(vscode): Usability enhancements - #268

Merged
Rod-Christensen merged 19 commits into
developfrom
feat/vscode-enhancements
Mar 18, 2026
Merged

Rod-Christensen merged 19 commits into
developfrom
feat/vscode-enhancements

Conversation

@Rod-Christensen

@Rod-Christensen Rod-Christensen commented Mar 17, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This branch is a comprehensive overhaul of how the VS Code extension manages, deploys, and connects to the RocketRide engine. The core theme is giving users full control over the engine lifecycle without leaving VS Code — previously, users had to manually manage processes, services, and Docker containers from the terminal.

1. Dynamic port allocation for local engine

The extension previously hardcoded port 5565 for local engine connections. This meant opening a second VS Code window would fail because the port was already taken. The engine now starts with --port=0 so the OS assigns a free port, and the extension parses the actual port from Uvicorn's startup log output. Each VS Code window gets its own isolated engine process.

2. Versioned engine installs with cross-process safety

Engine binaries were previously extracted into a single shared directory. Upgrading while another window was running the engine would overwrite the in-use binary — causing EBUSY on Windows and race conditions everywhere. Engines are now installed into versioned directories (<root>/engines/<tag>--<hash>/) with:

  • Cross-process lockfile (proper-lockfile) to prevent install races between multiple VS Code windows
  • Channel pointer files (current-stable.json, current-pre.json) to track which version is active per channel
  • PID files for safe cleanup of old version directories that aren't in use
  • Persistent storage moved from context.extensionPath to context.globalStorageUri.fsPath so engines survive extension updates

3. Local OS service deployment — install, start, stop, update, remove

Added a complete "Local Service" deployment path alongside Cloud and Docker. Users can install the RocketRide engine as a native background service that auto-starts on boot, managed entirely from the Deploy page:

  • Windows: Uses NSSM (Non-Sucking Service Manager) bundled with the engine
  • Linux: Generates and manages systemd unit files
  • macOS: Generates and manages launchd plist files
  • Common base class with TCP port-check utility shared across platforms

The PageDeployProvider orchestrates the lifecycle by combining EngineInstaller (download/version management) with ServiceManager (OS service registration). Installed version is tracked in a config file (ProgramData/RocketRide/config.json). Status polling runs every 3 seconds with a 60-second timeout when waiting for the service to reach "running" state.

The Deploy page UI gained a service status display (color-coded state indicator, version, install path), a split-button with version dropdown for install/update, start/stop/remove controls, and progress/error feedback streamed from the provider.

4. Docker container lifecycle management

The previous Docker deployment was fire-and-forget: it opened a terminal, ran docker pull and docker create, and the user had no further control from VS Code. This replaces it with a proper container manager mirroring the OS service panel:

  • DockerManager class using the dockerode SDK for install, start, stop, remove, update, and status operations. Handles Windows named-pipe detection (Docker Desktop vs standalone Docker) and maps Docker API errors to user-friendly messages.
  • ssh2 stub (src/stubs/ssh2.js) with an esbuild alias so that docker-modem (a dockerode transitive dependency) doesn't pull in native .node binaries. We only connect via local socket/named pipe, never SSH — the stub satisfies the require('ssh2').Client reference at zero cost.
  • GHCR tag fetching via the Docker Registry V2 anonymous token flow to populate the version dropdown with available image tags, separated into stable and prerelease.
  • Docker config persistence (docker-config.json) to track which version was installed, since Docker container inspect doesn't carry that metadata.
  • The React UI gives Docker its own status display, progress/error feedback, and split-button version selector — the same UX pattern as the OS service panel. Shared UI elements (renderSplitButton, renderInstalledActions, renderStatusIndicator) were refactored to be reusable across both panels.

5. Debug trace propagation and DAP logging

  • "Full debug output" checkbox in Settings (local + on-prem modes) that injects --trace=servicePython into engine args. One toggle replaces manual arg editing. The checkbox respects user overrides: if the user already typed --trace= in the Server Arguments field, the checkbox value is skipped to avoid duplicate-flag rejections from the C++ argparser.
  • Server-side trace inheritance: task_engine.py now inherits the parent engine's --trace setting from sys.argv when DAP args don't include one, so subprocess pipelines always get trace config. User args with spaces are safely split via shlex.split().
  • DAP request/response logging added to ConnectionManager — all commands (execute, launch, etc.) and their full payloads are now visible in the "RocketRide: Extension" output panel. Previously these were completely silent, making protocol-level debugging impossible.
  • Python 3.12 frozen-modules debugger warning suppressed in node.py.
  • Reconnection backoff cap reduced from 15s to 5s for faster recovery.

6. Settings and UI refinements

  • Debugger removed from package.json — activation events, breakpoints, debuggers contribution all removed. The adapter/session files are still compiled but not wired up; adapter.ts contains a restoration guide. The debugger was adding activation overhead and user confusion while non-functional during rework.
  • API key input moved from top-level into the On-prem panel (it's only relevant there).
  • Cloud mode shows a "Coming Soon" placeholder in both Settings and Welcome pages.
  • Engine arguments changed from string[] array editor to a single string text input.
  • Local port input removed — port is now dynamic and not user-facing.
  • "Engine Version" renamed to "Server Version" throughout.
  • Internal-only commands hidden from command palette (openStatus, openFile, runPipeline, stopPipeline, openPipelineAsText, page.status.open, setupCredentials).
  • Two new commands added: rocketride.provider.connection.focus and rocketride.provider.files.focus.
  • Integration settings component with toggle checkboxes for GitHub Copilot, Claude Code, Cursor, and Windsurf (persisted in VS Code settings, not yet wired to functionality).
  • Welcome page text colors changed to explicit rgba(255,255,255,...) values for consistent appearance on the dark branded background.
  • onprem.svg replaced with a server/gear icon that better represents local service deployment.
  • Progress bar styling updated to monospace font with text-overflow: ellipsis, better suited for streaming Docker pull output.

Test plan

  • Local engine: Open two VS Code windows with local mode — verify each gets its own engine on a different port, no port conflicts
  • Version management: Install engine, close VS Code, reopen — verify engine persists (not re-downloaded). Install a second version while the first is running — verify no EBUSY or corruption
  • OS service (Windows): Deploy page > Local Service > Install > verify NSSM service created and running. Stop/Start/Update/Remove all work. Service survives VS Code restart.
  • OS service (Linux): Same flow with systemd unit file
  • OS service (macOS): Same flow with launchd plist
  • Docker: Deploy page > Docker Container > Install with version selector. Verify pull progress streams to UI. Start/Stop/Update/Remove all work. "Docker unavailable" state shown when daemon not running.
  • Docker (Windows pipes): Test with both Docker Desktop and standalone Docker — verify named pipe fallback works
  • Debug trace: Enable "Full debug output" checkbox > run a pipeline > verify --trace=servicePython appears in engine output and child processes inherit it
  • DAP logging: Open output panel > "RocketRide: Extension" > verify DAP request/response payloads are logged
  • Settings UI: Verify mode switching (local/on-prem/cloud), API key only in on-prem, server args only in local, integrations checkboxes persist
image image image

Summary by CodeRabbit

  • New Features

    • Docker-based engine deploy & lifecycle (install/start/stop/update/remove/status)
    • Cross-platform OS service management (Windows, Linux, macOS) with lifecycle controls
    • Server version selector for local mode and dynamic local engine ports
  • UI / Pages

    • Deploy page redesigned with richer service/docker controls, status polling, version discovery, progress and sudo flows
    • New split-button controls and placeholder styling
  • Settings

    • Integration toggles: GitHub Copilot, Claude Code, Cursor, Windsurf
    • Local debug output option and simplified engine argument handling (now a single string)
  • Chores

    • Debugger integration disabled from activation; related activation events removed

Rod.Christensen added 4 commits March 16, 2026 13:24
Local mode no longer uses a hardcoded port (previously defaulted to 5565).
The engine now starts with --port=0 so the OS assigns a free port, and
the extension parses the actual port from Uvicorn's startup output.

Why: Multiple VS Code windows running local engines would collide on the
same port. Dynamic allocation lets each window get its own engine process
without conflicts.

Affected: engine-manager.ts, connection.ts, config.ts

Engine installs are now placed in versioned directories
(<enginesRoot>/engines/<tag>--<hash>/) instead of a single shared folder.
A cross-process lockfile (proper-lockfile) coordinates installs so multiple
VS Code windows don't race. Channel pointer files (current-stable.json,
current-pre.json) track which version is active. PID files allow safe
cleanup of old version directories that aren't in use.

Why: The previous single-directory model meant that upgrading the engine
while another window was running it would clobber the in-use binary.
On Windows this caused EBUSY errors; on all platforms it was a race
condition between download/extract and running processes.

Also moves the engine storage root from context.extensionPath to
context.globalStorageUri.fsPath so engines persist across extension
updates instead of being re-downloaded each time.

Affected: engine-installer.ts, engine-manager.ts, extension.ts,
          connection.ts, package.json (added proper-lockfile)

Removed debugger registration from package.json (activation events,
breakpoints contribution, debuggers contribution) and commented out
the registerDebugger() call in extension.ts. The adapter.ts and
session.ts files are still compiled but not wired up; adapter.ts now
contains a restoration guide in its header comment.

Why: The debugger is being reworked and was adding activation overhead
and user confusion while non-functional.

Affected: package.json, extension.ts, adapter.ts

- Moved API key input from a top-level "RocketRide account" section into
  the On-prem connection mode panel (it's only relevant there).
- Cloud mode now shows a "Coming Soon" placeholder in both Settings and
  Welcome pages.
- Mode-specific fields are wrapped in a bordered config box for clarity.
- Replaced the array-based engine arguments editor with a single text
  input (engineArgs changed from string[] to string).
- Added a "Full debug output" checkbox (local.debugOutput) that passes
  --trace=servicePython, shown instead of the manual args field when
  enabled.
- Removed the local port input — port is now dynamic and not user-facing.
- Renamed "Engine Version" to "Server Version" throughout the UI.
- Hidden internal-only commands from the command palette (file ops,
  status, setup credentials).
- Added two new commands: rocketride.provider.connection.focus and
  rocketride.provider.files.focus.

Added an IntegrationSettings component with toggle checkboxes for
GitHub Copilot, Claude Code, Cursor, and Windsurf. Backed by new
VS Code configuration keys (rocketride.integrations.*).

Why: Preparing for AI assistant integrations; the toggles are
persisted but not yet wired to functionality.

Affected: IntegrationSettings.tsx (new), PageSettings.tsx,
          PageSettingsProvider.ts, package.json

When the parent engine was started with --trace=..., child pipeline
processes now inherit that flag automatically (unless the user explicitly
set one). Uses the new startup_args() import from rocketlib.

Why: Without this, debugging child processes required manually adding
trace args to every pipeline launch configuration.

- Welcome page text colors changed from VS Code theme variables to
  explicit rgba(255,255,255,...) values for consistent appearance on
  the dark branded background.
- Mode description moved inside a config box with border.
- MUI InputLabel style overrides added for smaller italic labels.
- Removed padding from MuiInputBase-input.
- Placeholder text styled with italic/light/reduced opacity.
- Reconnection backoff cap reduced from 15s to 5s.
…efinements

Add a "Full debug output" checkbox to Settings (local + on-prem modes) that
injects --trace=servicePython into engine args. This gives users a single
toggle for detailed trace logging without manually editing server arguments.
The trace flag is sent both to the local engine command line and via DAP
execute/launch requests, so task subprocesses receive it too.

The trace flag respects user overrides: if the user types their own --trace=
in the Server Arguments field, the checkbox's value is skipped to avoid
duplicates — the engine's C++ argparser rejects duplicate --trace entries.

On the server side, task_engine.py now inherits the parent engine's --trace
setting from sys.argv when the DAP args don't include one, ensuring
subprocesses always get trace config regardless of how the engine was started.
User-provided args containing spaces are safely split using shlex.split()
to handle quoted paths like --path='C:\Program Files'.

DAP request/response logging added to ConnectionManager so all commands
(execute, launch, etc.) and their full payloads are visible in the
RocketRide: Extension output panel — previously these were completely silent,
making debugging difficult.

Other refinements:
- Server Arguments field now only shown in local mode (not on-prem)
- "when VS Code starts" → "when extension starts" for accuracy
- Suppress Python 3.12 frozen modules debugger warning in node.py
- Reduce reconnection backoff cap from 15s to 5s
Add the ability to install, update, start, stop, and remove the
RocketRide engine as a native OS service directly from the Deploy page.
This supplements the existing Cloud and Docker deployment options with a
third "Local Service" path that runs the engine as a background service
that starts automatically on boot.

Key changes:

- New `deploy/` module with platform-specific service managers:
  - Windows: uses NSSM (Non-Sucking Service Manager)
  - Linux: uses systemd unit files
  - macOS: uses launchd plist files
  - Common `ServiceManager` abstract base class with port-check utility

- PageDeployProvider: orchestrates service lifecycle by combining
  EngineInstaller (download/version management) with ServiceManager
  (OS service registration). Tracks installed version in a config file
  at ProgramData/RocketRide/config.json. Adds status polling (3s
  interval) and a waitForServiceRunning loop with 60s timeout.

- PageDeploy.tsx: new service status display (state indicator, version,
  install path), split-button UI with version dropdown for install and
  update actions, start/stop/remove controls, progress and error
  feedback via messages from the provider.

- styles.css: service status indicators (color-coded states), split
  button with dropdown, progress/error message styles.

- engine-installer.ts: extract `getLatestPointer()` to deduplicate the
  channel-fallback logic across getExecutablePath, getInstalledDir,
  getInstalledVersion, and getInstalledPublishedAt.

- engine-manager.ts: respect configured engineVersion setting when
  reading version info (prerelease vs stable channel).

- onprem.svg: replaced chip icon with a server/gear icon that better
  represents local service deployment.
…r lifecycle management

The previous Docker deployment was a fire-and-forget approach that opened a
terminal, ran `docker pull` and `docker create`, and gave the user no
feedback or control afterward. This meant users had no way to start, stop,
update, or remove the container from within VS Code — they had to drop to
the CLI for everything after initial setup.

This commit replaces that with a proper Docker container manager that
mirrors the existing OS service (NSSM/systemd/launchd) deployment panel:

- Add `DockerManager` class (`docker-manager.ts`) using the dockerode SDK
  to provide install, start, stop, remove, update, and status operations.
  Handles Windows named-pipe detection (Docker Desktop vs standalone) and
  maps Docker API errors to user-friendly messages.

- Add `ssh2` stub (`src/stubs/ssh2.js`) and esbuild alias so that
  docker-modem (a dockerode dependency) doesn't pull in native .node
  binaries — we only use local socket connections, never SSH tunnels.

- Extend `PageDeployProvider` backend with Docker lifecycle commands
  (dockerInstall, dockerRemove, dockerUpdate, dockerStart, dockerStop),
  Docker status polling, and GHCR tag fetching via the Docker Registry V2
  anonymous token flow to populate the version dropdown.

- Add Docker config persistence (`docker-config.json` in ProgramData /
  /Library/Application Support / /etc) to track which version was
  installed, since Docker container inspect doesn't carry that metadata.

- Rework the React `PageDeploy` UI to give Docker its own status display,
  progress/error feedback, and split-button version selector — the same
  UX pattern already used by the OS service panel. Refactor shared UI
  elements (renderSplitButton, renderInstalledActions, renderStatusIndicator)
  to be reusable across both panels instead of duplicated.

- Update progress bar styling to use monospace font with text-overflow
  ellipsis, better suited for streaming Docker pull output.

- Add `dockerode` and `@types/dockerode` as dependencies; sort
  existing deps alphabetically in package.json.
@github-actions github-actions Bot added module:vscode VS Code extension module:ai AI/ML modules module:ui Chat UI and Dropper UI labels Mar 17, 2026
@coderabbitai

coderabbitai Bot commented Mar 17, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Introduces platform service and Docker managers, versioned engine installer, dynamic local engine ports and PID tracking, switches engineArgs to a single string with effective-args injection, updates VS Code manifest/settings (local.debugOutput, integrations), rewrites Deploy/Settings UIs, and plugs Docker/SSH stubbing and build aliasing.

Changes

Cohort / File(s) Summary
Launch & Manifest
\.vscode/launch.json, apps/vscode/package.json
Adds --trace=servicePython to launch args; major VS Code manifest overhaul: trimmed activation events, removed debugger/breakpoints, added rocketride.local.debugOutput and integration flags, changed engineArgs shape, updated commands/menus and dependencies.
Config & Engine args
apps/vscode/src/config.ts
engineArgs changed from string[] → string; local.host/port removed; local.debugOutput added; new getEffectiveEngineArgs() injects trace when appropriate.
Connection & Engine runtime
apps/vscode/src/connection/...
apps/vscode/src/connection/connection.ts, engine-installer.ts, engine-manager.ts
Switch from extensionPath→enginesRoot, dynamic engine startup with OS-assigned port and PID file tracking, EngineInstaller reworked to versioned dirs with cross-process lock and GitHub release handling, new public APIs for installer/port info.
Service management (abstraction + platforms)
apps/vscode/src/deploy/service-manager.ts, service-linux.ts, service-mac.ts, service-windows.ts
New ServiceManager abstraction and platform implementations (systemd, launchd, NSSM) providing install/update/remove/start/stop/getStatus, elevation handling and port checks.
Docker manager & stubs
apps/vscode/src/deploy/docker-manager.ts, apps/vscode/src/stubs/ssh2.js, apps/vscode/esbuild.js
New DockerManager (install/start/stop/update/remove/status), stubbed ssh2 module and esbuild alias to avoid native .node loading.
Deploy provider & UI
apps/vscode/src/providers/PageDeployProvider.ts, apps/vscode/src/providers/views/PageDeploy/*, apps/vscode/src/providers/views/PageDeploy/styles.css
Rewrote Deploy provider and React UI to use ServiceManager + DockerManager, added version discovery (GitHub/GHCR), richer message protocol, split-button UI, persistent configs, polling, and detailed CSS.
Settings & Integration UI
apps/vscode/src/providers/PageSettingsProvider.ts, .../PageSettings/*, apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx
Settings shape updated (localEngineArgs: string, localDebugOutput, integration flags); new IntegrationSettings component and mode-config-box UI; save/load wired to new keys.
Engine arg callers
apps/vscode/src/debugger/session.ts, apps/vscode/src/providers/PageStatusProvider.ts, apps/vscode/src/providers/SidebarFilesProvider.ts
Replaced direct config.engineArgs usage with getEffectiveEngineArgs() for launches and pipeline runs.
Extension activation & docs
apps/vscode/src/extension.ts, apps/vscode/src/debugger/*, docs/README-vscode.md
Disabled debugger registration in activation; switched to setEnginesRoot(...); updated debugger header comments and README settings docs for new integrations.
Welcome & Connection UIs
apps/vscode/src/providers/views/PageWelcome/*, apps/vscode/src/providers/views/PageSettings/ConnectionSettings.tsx
Removed local-port auto-assignment, simplified On-prem/Local flows, moved server-version selector into local mode, reorganized UI blocks.
Styling & theme
apps/vscode/src/providers/styles/vscode.css, packages/shared-ui/src/theme.ts
Added placeholder styling and VS Code input label overrides; various CSS additions for Deploy/Settings UIs.
Python & Node runtime tweaks
packages/ai/src/ai/modules/task/task_engine.py, packages/ai/src/ai/node.py
Child arg splitting for space-containing args and inherit parent's --trace= into child args; suppressed some debugpy/node debug logs and set PYDEVD_DISABLE_FILE_VALIDATION.
Misc / packaging
package.json
Minor pnpm overrides formatting and devDependency reorderings.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant VSCode
    participant ConfigManager
    participant EngineInstaller
    participant EngineManager
    participant Engine
    VSCode->>ConfigManager: getEffectiveEngineArgs()
    ConfigManager-->>VSCode: args (may include --trace=servicePython)
    VSCode->>EngineInstaller: install(versionSpec)
    EngineInstaller->>EngineInstaller: acquire install.lock
    EngineInstaller->>GitHub: fetch release asset
    GitHub-->>EngineInstaller: asset
    EngineInstaller->>Filesystem: extract to versioned dir
    EngineInstaller->>EngineInstaller: update pointers, release lock
    EngineInstaller-->>VSCode: executablePath
    VSCode->>EngineManager: start(executablePath, port=0)
    EngineManager->>Engine: launch (port=0)
    Engine-->>EngineManager: ready on dynamic port
    EngineManager-->>VSCode: actualPort
Loading
sequenceDiagram
    actor User
    participant VSCode
    participant DockerManager
    participant GitHubCR
    participant DockerDaemon
    participant Container
    User->>VSCode: install docker-based runtime
    VSCode->>DockerManager: install(imageTag)
    DockerManager->>GitHubCR: pull image
    GitHubCR-->>DockerDaemon: stream layers
    DockerManager->>DockerDaemon: create & start container (bind 5565)
    DockerDaemon->>Container: launch
    Container-->>DockerDaemon: running
    DockerManager-->>VSCode: status/progress
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested reviewers

  • jmaionchi
  • stepmikhaylov
  • dsapandora

Poem

🐰 I nudged the ports and pointed pins,

I coaxed locked versions from their bins,
I stubbed a module, pulled an image bright,
Injected traces so the logs take flight —
Hooray, the engines hum through night!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'feat(vscode): Usability enhancements' is vague and does not accurately convey the major scope of changes, which involve comprehensive engine lifecycle management, versioned deployments, service installation, Docker integration, and debug tracing enhancements. Consider a more specific title that reflects the main architectural changes, such as 'feat(vscode): Engine lifecycle management with versioned deployment and service integration' or breaking into multiple focused commits with clearer titles.
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/vscode-enhancements
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread apps/vscode/src/connection/engine-installer.ts Dismissed
Comment thread apps/vscode/src/deploy/service-manager.ts Fixed
Comment thread apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx Fixed
Comment thread packages/ai/src/ai/modules/task/task_engine.py Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/vscode/src/connection/engine-installer.ts (1)

548-559: ⚠️ Potential issue | 🟠 Major

Filter the prerelease channel to actual prerelease releases.

This find() does not check r.prerelease, so the <Prerelease> option will usually resolve to the newest stable release whenever stable has been published more recently than the latest prerelease.

🐛 Minimal fix
-			const pre = data.find(r => r.tag_name.startsWith('server-') && r.assets && r.assets.length > 0);
+			const pre = data.find(r => r.tag_name.startsWith('server-') && r.prerelease && r.assets && r.assets.length > 0);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/connection/engine-installer.ts` around lines 548 - 559, The
prerelease branch that handles versionSpec === 'prerelease' currently selects a
release via data.find(...) but fails to require r.prerelease, causing stable
releases to be chosen; update the predicate used when searching releases (the
code around octokit.repos.listReleases and the local variable pre) to also check
r.prerelease === true in addition to tag_name startingWith('server-') and
assets.length > 0, then continue returning this.toReleaseInfo(pre) as before.
🧹 Nitpick comments (11)
apps/vscode/src/providers/views/PageWelcome/styles.css (1)

95-158: Consolidate repeated left-panel tint values into local CSS variables.

The new colors are fine, but the same rgba(255,255,255,...) literals now repeat across multiple selectors. Centralizing them in .welcome-left will simplify future theme tuning.

♻️ Suggested refactor
 .welcome-left {
 	width: 280px;
 	min-width: 280px;
 	background: linear-gradient(180deg, `#1a1a3a` 0%, `#252545` 100%);
+	--welcome-left-text-subtle: rgba(255, 255, 255, 0.6);
+	--welcome-left-text-muted: rgba(255, 255, 255, 0.75);
+	--welcome-left-text-strong: rgba(255, 255, 255, 0.9);
+	--welcome-left-divider: rgba(255, 255, 255, 0.2);
 	padding: 40px 30px;
 	display: flex;
 	flex-direction: column;
 	align-items: center;
 	border-right: 1px solid var(--vscode-widget-border);
 }
@@
 .welcome-brand-sub {
 	font-size: 12px;
-	color: rgba(255, 255, 255, 0.6);
+	color: var(--welcome-left-text-subtle);
@@
 .welcome-tagline {
@@
-	color: rgba(255, 255, 255, 0.75);
+	color: var(--welcome-left-text-muted);
@@
 .welcome-features li {
@@
-	color: rgba(255, 255, 255, 0.9);
+	color: var(--welcome-left-text-strong);
@@
 .welcome-feature-icon {
-	color: rgba(255, 255, 255, 0.9);
+	color: var(--welcome-left-text-strong);
@@
 .welcome-divider {
@@
-	background: rgba(255, 255, 255, 0.2);
+	background: var(--welcome-left-divider);
@@
 .welcome-links a {
-	color: rgba(255, 255, 255, 0.75);
+	color: var(--welcome-left-text-muted);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/providers/views/PageWelcome/styles.css` around lines 95 -
158, Multiple selectors (.welcome-brand-sub, .welcome-tagline, .welcome-features
li, .welcome-feature-icon, .welcome-links a, .welcome-links a:hover,
.welcome-divider) repeat rgba(255,255,255,...) colors; define local CSS custom
properties on .welcome-left (e.g. --left-fg, --left-fg-strong, --left-fg-muted,
--left-divider) and replace the repeated rgba literals in those classes with the
new variables so future theme tweaks only change .welcome-left's variable
values.
packages/shared-ui/src/theme.ts (1)

327-338: Scope and normalize MuiInputLabel VSCode styling to avoid low-readability defaults.

This override affects all input labels in VSCode and forces a very low-emphasis style (italic + light + low opacity). Consider keeping global labels readable and size them from baseFontSize, then apply compact/italic styling only where explicitly needed.

Suggested adjustment
 			MuiInputLabel: {
 				styleOverrides: {
 					root: inVSCode
 						? {
-								fontSize: '0.75rem',
-								fontStyle: 'italic',
-								fontWeight: 300,
-								opacity: 0.7,
+								fontSize: `${Math.max(baseFontSize - 1, 11)}px`,
+								fontWeight: 400,
 							}
 						: {},
 				},
 			},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared-ui/src/theme.ts` around lines 327 - 338, The current
MuiInputLabel override applies an italic, light-weight, low-opacity style
globally when inVSCode, reducing readability; change the global override in the
MuiInputLabel -> styleOverrides -> root block to preserve readable defaults (use
fontSize derived from baseFontSize, no forced italic or reduced opacity), and
move the compact/italic/light/low-opacity presentation into a scoped alternative
(e.g., a new CSS class or theme variant such as "compactInputLabel" or a
component override for specific components) so only labelled compact inputs
receive fontStyle: 'italic', fontWeight: 300 and opacity: 0.7; reference the
inVSCode flag to adjust only sizing (e.g., fontSize: `${baseFontSize *
0.75}rem`) while removing global fontStyle/fontWeight/opacity changes from
MuiInputLabel.root.
packages/ai/src/ai/modules/task/task_engine.py (1)

46-46: Unused import detected.

Static analysis indicates debug is imported but not used in this file. If it's no longer needed after recent changes, consider removing it.

♻️ Proposed fix
-from rocketlib import debug, args as startup_args
+from rocketlib import args as startup_args
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ai/src/ai/modules/task/task_engine.py` at line 46, The import list
includes an unused symbol `debug` from `rocketlib` in the statement that also
imports `args as startup_args`; remove `debug` from that import (leaving `args
as startup_args`) to eliminate the unused import, or if `debug` was intended to
be used, add the necessary usage in this module (e.g., replace existing logging
calls to use `debug`)—locate the import line with `from rocketlib import debug,
args as startup_args` and update it accordingly.
apps/vscode/src/providers/views/PageSettings/ConnectionSettings.tsx (1)

228-241: Consider semantic grouping for version dropdown.

The disabled separator option (────────────────) may cause accessibility issues as screen readers could announce it as a selectable option. Using <optgroup> provides proper semantic grouping.

♻️ Accessible alternative using optgroup
 <select
     id="serverVersion"
     value={settings.localEngineVersion}
     onChange={handleVersionChange}
     disabled={engineVersionsLoading}
 >
-    <option value="latest">&lt;Latest&gt;</option>
-    <option value="prerelease">&lt;Prerelease&gt;</option>
-    {engineVersions.length > 0 && (
-        <option disabled>{'────────────────'}</option>
-    )}
+    <optgroup label="Quick Select">
+        <option value="latest">&lt;Latest&gt;</option>
+        <option value="prerelease">&lt;Prerelease&gt;</option>
+    </optgroup>
     {engineVersionsLoading && (
         <option disabled>Loading versions...</option>
     )}
-    {engineVersions.map(v => (
-        <option key={v.tag_name} value={v.tag_name}>
-            {displayVersion(v.tag_name)}
-        </option>
-    ))}
+    {engineVersions.length > 0 && (
+        <optgroup label="Specific Versions">
+            {engineVersions.map(v => (
+                <option key={v.tag_name} value={v.tag_name}>
+                    {displayVersion(v.tag_name)}
+                </option>
+            ))}
+        </optgroup>
+    )}
 </select>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/providers/views/PageSettings/ConnectionSettings.tsx` around
lines 228 - 241, Replace the visual disabled separator option with semantic
optgroup(s) for the versions dropdown: wrap the static options ("latest",
"prerelease") in one optgroup (e.g., label "Recommended") and the dynamic
engineVersions map in a separate optgroup (e.g., label "All versions"), keeping
the existing option elements created by engineVersions.map(v => <option
key={v.tag_name} value={v.tag_name}>{displayVersion(v.tag_name)}</option>), and
move the loading state option into the dynamic optgroup (or replace it with a
disabled optgroup label) so screen readers get proper grouping instead of a
disabled pseudo-separator.
apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx (1)

32-53: Consider narrowing the type for better type safety.

The key type is keyof SettingsData which includes non-boolean fields. While the current implementation works because INTEGRATIONS only contains boolean keys, the type could be more precise.

♻️ Type-safe alternative
-const INTEGRATIONS: { key: keyof SettingsData; label: string; description: string }[] = [
+type IntegrationKey = 'integrationCopilot' | 'integrationClaudeCode' | 'integrationCursor' | 'integrationWindsurf';
+
+const INTEGRATIONS: { key: IntegrationKey; label: string; description: string }[] = [

This ensures only boolean integration keys can be used and provides compile-time safety if SettingsData changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx` around
lines 32 - 53, INTEGRATIONS uses key: keyof SettingsData which may include
non-boolean fields; tighten the type by declaring a helper mapped type (e.g.,
BooleanKeys<T> = { [K in keyof T]: T[K] extends boolean ? K : never }[keyof T])
and change INTEGRATIONS to use key: BooleanKeys<SettingsData> so only boolean
integration keys (like integrationCopilot, integrationClaudeCode,
integrationCursor, integrationWindsurf) are allowed; update any related
references expecting keyof SettingsData to accept the narrowed
BooleanKeys<SettingsData> where needed.
apps/vscode/src/deploy/service-manager.ts (1)

22-22: Remove unused import.

The icons import is not used in this file. The static analysis tool correctly flagged this.

🧹 Proposed fix
 import * as net from 'net';
 import { getLogger } from '../shared/util/output';
-import { icons } from '../shared/util/icons';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/deploy/service-manager.ts` at line 22, The file imports the
unused symbol `icons` from '../shared/util/icons'; remove that unused import to
clean up the code and satisfy static analysis. Locate the import statement
`import { icons } from '../shared/util/icons';` in service-manager.ts and delete
it (or remove `icons` from the import list if multiple symbols are imported),
then run lint/typecheck to confirm the warning is resolved.
apps/vscode/src/deploy/service-windows.ts (1)

248-271: Consider adding redirect depth limit.

The downloadFile method recursively follows HTTP redirects without a depth limit. While rare, a malicious or misconfigured server could cause a stack overflow with redirect loops.

🛡️ Proposed fix
-	private downloadFile(url: string, destPath: string): Promise<void> {
+	private downloadFile(url: string, destPath: string, maxRedirects: number = 5): Promise<void> {
 		return new Promise((resolve, reject) => {
 			const protocol = url.startsWith('https') ? https : http;
 			const req = protocol.get(url, { headers: { 'User-Agent': 'RocketRide-VSCode' } }, (response) => {
 				if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
+					if (maxRedirects <= 0) {
+						response.destroy();
+						reject(new Error('Too many redirects'));
+						return;
+					}
 					response.destroy();
-					this.downloadFile(response.headers.location, destPath).then(resolve, reject);
+					this.downloadFile(response.headers.location, destPath, maxRedirects - 1).then(resolve, reject);
 					return;
 				}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/deploy/service-windows.ts` around lines 248 - 271, The
downloadFile function follows redirects recursively with no depth limit; modify
downloadFile(url: string, destPath: string, maxRedirects = 10) to track
remaining redirects and decrement on recursive calls, and when
response.headers.location is present call this.downloadFile(newLocation,
destPath, maxRedirects - 1) and reject with an explicit error if maxRedirects <=
0; ensure the existing request/response cleanup (response.destroy(),
file.close()) and req.on('error') behavior remain unchanged.
apps/vscode/src/providers/PageDeployProvider.ts (2)

365-365: Replace Function type with explicit signature.

Using Function loses type safety. Define the expected method signatures explicitly.

♻️ Proposed fix
-			https.get(url, options, (res: { statusCode?: number; on: Function; setEncoding: Function }) => {
+			https.get(url, options, (res: { statusCode?: number; on: (event: string, listener: (data?: string) => void) => void; setEncoding: (encoding: BufferEncoding) => void }) => {

Alternatively, import and use the IncomingMessage type from Node's http module for full type safety:

+import type { IncomingMessage } from 'http';
 ...
-			https.get(url, options, (res: { statusCode?: number; on: Function; setEncoding: Function }) => {
+			https.get(url, options, (res: IncomingMessage) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/providers/PageDeployProvider.ts` at line 365, The inline
callback in PageDeployProvider.ts currently types the response parameter as {
statusCode?: number; on: Function; setEncoding: Function }, which uses the
unsafe Function type; change it to a proper shape or use Node's IncomingMessage
type from 'http' (or import IncomingMessage from 'http' and type the callback
param as res: IncomingMessage) and replace on: Function and setEncoding:
Function with their actual signatures (e.g., on(event: string, listener:
(...args: any[]) => void): this and setEncoding(encoding?: string): void) so the
https.get callback has explicit, type-safe method signatures.

558-561: Remove unused getApiKey method.

This method is defined but never called anywhere in the file.

🧹 Proposed fix
-	private async getApiKey(): Promise<string> {
-		const config = ConfigManager.getInstance().getConfig();
-		return config.apiKey || '';
-	}
-
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/providers/PageDeployProvider.ts` around lines 558 - 561, The
private method getApiKey in PageDeployProvider is unused and should be removed
to clean up dead code; delete the entire getApiKey() { const config =
ConfigManager.getInstance().getConfig(); return config.apiKey || ''; } method
from the PageDeployProvider class (ensure no references remain to getApiKey
elsewhere and run a quick project-wide search for getApiKey to confirm).
apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx (2)

13-25: Type duplication from backend files.

ServiceStatus and DockerStatus interfaces duplicate types from service-manager.ts and docker-manager.ts. This is acceptable for webview isolation (webview code runs in a separate context), but consider adding a comment noting the intentional duplication to prevent future drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx` around lines 13 -
25, The ServiceStatus and DockerStatus interfaces in PageDeploy.tsx
intentionally duplicate types defined in service-manager.ts and
docker-manager.ts for webview isolation; add a short clear comment above both
interface declarations stating that this duplication is deliberate (webview runs
in a separate context) and should be kept in sync with the backend types to
prevent accidental drift, referencing the backend files by name
(service-manager.ts, docker-manager.ts) to guide future maintainers.

97-100: Remove or use versionsLoading state.

The versionsLoading state is set but never read. If you intended to show a loading indicator while fetching versions, you should add the UI for it; otherwise, remove the unused state.

🧹 Option 1: Remove if not needed
 	// Service version state
 	const [versions, setVersions] = useState<VersionItem[]>([]);
-	const [versionsLoading, setVersionsLoading] = useState(false);
 	const [selectedVersion, setSelectedVersion] = useState('latest');

And remove the setter calls at lines 165 and 154.

🔧 Option 2: Use for loading indicator

Add a loading indicator in the version dropdown or split button when versionsLoading is true.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx` around lines 97 -
100, The versionsLoading state is declared but never read; either remove it and
all calls to its setter (remove the useState hook for versionsLoading and any
calls to setVersionsLoading) or keep it and wire it into the UI by showing a
loading indicator in the version selector (e.g., disable the version
dropdown/split button and show a spinner when versionsLoading is true).
Specifically update the PageDeploy component: delete the const [versionsLoading,
setVersionsLoading] = useState(false) and remove setVersionsLoading(...) calls
if opting for removal, or if opting to use it, read versionsLoading where the
version dropdown or split button is rendered (referencing the versions,
selectedVersion, and setSelectedVersion state) to conditionally render a
spinner/disabled state while versions are loading.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/vscode/src/config.ts`:
- Around line 465-483: getEffectiveEngineArgs currently pushes the entire
engineArgs string as one element, causing spawn() to receive a single combined
arg; update getEffectiveEngineArgs to split argsStr into individual tokens
before adding to result (e.g. const argsArray =
argsStr.trim().split(/\s+/).filter(Boolean); result.push(...argsArray);), keep
the existing hasTrace check and still push '--trace=servicePython' as a separate
element when needed, and ensure the function returns an array of separate
argument strings for EngineManager.start() to spread into spawn().

In `@apps/vscode/src/connection/engine-installer.ts`:
- Around line 350-387: The cleanupOldVersions routine currently runs without the
installer lock and can remove dirs another process is extracting or that are
still pointed-to by channel pointers; update callers to invoke
cleanupOldVersions while holding the same installer lock used by the installer
(i.e., ensure engine-manager calls cleanupOldVersions under the existing
install/lock mechanism) and modify cleanupOldVersions to first load and preserve
pointer targets from current-stable.json and current-pre.json (skip deleting any
directory referenced there) before removing others; retain the existing
hasLiveProcesses(dirPath) check and error handling but ensure the overall method
is executed while holding the installer lock so deletions are cross-process
safe.

In `@apps/vscode/src/connection/engine-manager.ts`:
- Around line 206-214: The PID file is stored in mutable instance state
(this.pidFilePath) and removed by multiple async callbacks (stopProcess timeout
and child 'exit'), which can race and delete a PID file belonging to a newly
started child; fix by scoping the PID file to the specific child that created
it—capture the child.pid (or attach a pidFilePath property to the ChildProcess)
when creating the file in the block that writes it and update
removePidFile()/exit/timeout handlers to only remove the file if the PID (or
pidFilePath) matches that captured value (or the same child instance), leaving
this.pidFilePath as a derived/current-only property; update references in
removePidFile, stopProcess, and the child 'exit' handler (symbols:
this.pidFilePath, removePidFile(), stopProcess(), this.child, the child's 'exit'
listener) to perform the check before unlinking.
- Around line 244-255: tryResolveReady currently marks the server ready even
when portRegex doesn't match, leaving this.actualPort undefined; change
tryResolveReady so it only sets this.actualPort, processReady, this.started,
logs readiness, and calls resolve() after a successful regex match (i.e., when
match is truthy); leave other branches untouched (do not mark ready or resolve
when match is falsy) and reference tryResolveReady, portRegex, this.actualPort,
processReady, processErrored, this.started, logger.output, and resolve() when
making the change.
- Around line 144-151: The ConfigManager.getEffectiveEngineArgs() currently
returns the whole engineArgs string as one element; update
getEffectiveEngineArgs() (in apps/vscode/src/config.ts) to tokenize the
engineArgs string into individual arguments before returning (e.g., split on
whitespace while preserving quoted groups or use a simple shell-like parse) so
callers like engine-manager.ts (uses effectiveArgs spread),
PageStatusProvider.ts and session.ts receive separate flag tokens (e.g.,
"--foo=1" and "--bar=2") instead of a single combined token.

In `@apps/vscode/src/deploy/docker-manager.ts`:
- Around line 200-206: The update() flow currently calls this.remove(true)
before await this.pullImage(fullImage,...), which risks deleting the running
deployment if the image pull fails; change update() to call await
this.pullImage(fullImage, onProgress) first (and authenticate as needed), then
create/start the new container (swap in the new image), verify it is
running/healthy, and only after successful start call this.remove(true) to
remove the old container/image; reference the update(), pullImage(), and
remove(true) calls to locate and reorder these steps and add basic health/start
verification before cleanup.
- Around line 121-127: The PortBindings currently expose `${CONTAINER_PORT}/tcp`
on all interfaces; update the container creation in docker-manager.ts so each
PortBindings entry for `${CONTAINER_PORT}/tcp` includes HostIp: '127.0.0.1'
(alongside HostPort: String(CONTAINER_PORT)) to bind the published port to
loopback. Locate the block using ExposedPorts, HostConfig and PortBindings
(references: CONTAINER_PORT, ExposedPorts, HostConfig, PortBindings) and apply
the same change to the second createContainer call elsewhere in the file (the
other occurrence around the lines noted).

In `@apps/vscode/src/deploy/service-linux.ts`:
- Around line 69-82: The update() method currently writes the temporary unit
file into INSTALL_ROOT which may be unwritable; change it to create the temp
file in the OS temp directory (use os.tmpdir()) instead of INSTALL_ROOT—build
the unitContent via buildUnitFile(executablePath, engineDir), write the tmp file
to path.join(os.tmpdir(), 'rocketride.service.tmp'), then use runSudo('cp',
[tmpUnit, UNIT_PATH]) and fs.unlinkSync(tmpUnit) to copy and clean up; ensure
the same cleanup behavior and systemctl calls (daemon-reload/start) remain
unchanged.
- Around line 36-50: The install method can fail creating /opt/rocketride and
writing the temp unit due to lack of permissions; replace the direct
fs.mkdirSync(INSTALL_ROOT, ...) with an elevated directory creation (e.g., await
this.runSudo('mkdir', ['-p', INSTALL_ROOT]) or await this.runSudo('install',
['-d', INSTALL_ROOT])) before touching files, and write the temporary unit file
to a user-writable temp directory (os.tmpdir()) as tmpUnit instead of under
INSTALL_ROOT so fs.writeFileSync(tmpUnit, ...) won’t require root; keep the sudo
cp to UNIT_PATH and the subsequent runSudo calls for systemctl (UNIT_NAME)
as-is.

In `@apps/vscode/src/deploy/service-mac.ts`:
- Around line 66-78: The update method currently writes the temporary plist to
INSTALL_ROOT which can trigger permission issues; change the temp file creation
in update() to use the system temp directory (os.tmpdir()) instead of
INSTALL_ROOT (e.g., build tmpPlist with path.join(os.tmpdir(),
'rocketride.plist.tmp')), keep the rest of the flow (writeFileSync, runSudo cp
to PLIST_PATH, unlinkSync) the same, and ensure you still call
buildPlist(executablePath, engineDir) to generate contents before copying.
- Around line 139-150: The runSudo function builds the AppleScript command by
naively wrapping args with single quotes which fails for arguments that contain
single quotes; fix it by adding a proper escaping step used when constructing
the script string (the script variable in runSudo) — implement a small escape
function (used inside runSudo) that replaces each single quote in an argument
with the safe shell-quoted sequence (so the argument can be safely wrapped in
single quotes), then use that escaped value instead of `'${a}'` when joining
args; keep the rest of runSudo (execFile('osascript', …)) unchanged.
- Around line 36-48: The install method currently creates LOGS_DIR and writes a
temporary plist under INSTALL_ROOT without elevation, which will fail on
/Library paths; change it to create the logs directory with elevated privileges
(call this.runSudo('mkdir', ['-p', LOGS_DIR']) or equivalent) and avoid writing
the temp plist directly to INSTALL_ROOT — instead write the tmpPlist to a
writable location like os.tmpdir() (use a variable tmpPlist constructed from
os.tmpdir()) and then use this.runSudo('cp', [tmpPlist, PLIST_PATH]) to move it
into place; keep using buildPlist to generate the content and remove the tmp
file after copying, and optionally set ownership/permissions on PLIST_PATH via
runSudo if needed.

In `@apps/vscode/src/providers/PageDeployProvider.ts`:
- Around line 385-387: CONFIG_PATH is Windows-only; update the static readonly
CONFIG_PATH in PageDeployProvider so it picks a platform-appropriate default
instead of falling back to 'C:\\ProgramData'. Replace the current single join
with a conditional using process.platform (e.g., if process.platform === 'win32'
use process.env.PROGRAMDATA || 'C:\\ProgramData', else use a Unix/macOS-friendly
path such as process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config')
and then path.join(..., 'RocketRide', 'config.json')); mirror the approach used
by DOCKER_CONFIG_PATH (same file) to ensure consistent cross-platform behavior
and keep the identifier CONFIG_PATH unchanged. Ensure path and os (if used) are
imported and the value remains a static readonly field.

In `@apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx`:
- Around line 211-218: The inline ref callback on the "split-button" div is
adding a document click listener without a proper cleanup (causing a leak);
replace that pattern by creating a stable ref (e.g., splitButtonRef) and move
the add/remove logic into a useEffect which registers a single handler that uses
splitButtonRef.current and calls setDropdownOpen(null) when clicks occur outside
the element, and ensure the effect returns a cleanup that removes the listener;
update the div to use ref={splitButtonRef} and keep the handler and
setDropdownOpen references stable or include them in the effect dependencies as
appropriate.

In `@apps/vscode/src/providers/views/PageSettings/PageSettings.tsx`:
- Around line 135-141: SettingsData declares localEngineArgs as a string but the
PageSettings initial state sets it to an empty array; change the initial value
of localEngineArgs in the PageSettings component state from [] to "" so the
initial state type matches the SettingsData interface (look for the SettingsData
interface and the localEngineArgs entry in the initial state object in
PageSettings.tsx).

In `@apps/vscode/src/stubs/ssh2.js`:
- Around line 1-5: Add the Node ESLint pragma to the top of the ssh2 stub so
ESLint knows Node globals are allowed: insert the comment /* eslint-env node */
as the first line of the file that defines module.exports = { Client: function()
{} }; to avoid no-undef errors for module and other Node globals while keeping
the existing stub implementation intact.

In `@packages/ai/src/ai/modules/task/task_engine.py`:
- Around line 1454-1458: The loop that expands user_args into child_args uses
shlex.split(arg) but does not guard against ValueError from malformed quotes,
which can crash task startup; update the loop in task_engine.py (the section
handling user_args -> child_args) to wrap shlex.split(arg) in a try/except for
ValueError, log or surface a clear error message (including the offending arg
and the exception) and fall back to a safe behavior (e.g., append the original
arg or reject the task with a clear error) so task startup won't fail with an
unclear traceback.

In `@packages/ai/src/ai/node.py`:
- Around line 103-104: The current except block silently swallows all exceptions
during debugger initialization (the except Exception as e: pass), which hides
real errors; update the exception handling in the debugger initialization code
to log the caught exception using the imported debug helper (e.g., debug.error
or debug.exception) and/or restrict the except to expected exceptions only
(e.g., OSError, PermissionError) so unexpected errors still surface; locate the
try/except around the debugger start in packages/ai/src/ai/node.py and replace
the pass with a logged message that includes the exception object (e) and
context.

---

Outside diff comments:
In `@apps/vscode/src/connection/engine-installer.ts`:
- Around line 548-559: The prerelease branch that handles versionSpec ===
'prerelease' currently selects a release via data.find(...) but fails to require
r.prerelease, causing stable releases to be chosen; update the predicate used
when searching releases (the code around octokit.repos.listReleases and the
local variable pre) to also check r.prerelease === true in addition to tag_name
startingWith('server-') and assets.length > 0, then continue returning
this.toReleaseInfo(pre) as before.

---

Nitpick comments:
In `@apps/vscode/src/deploy/service-manager.ts`:
- Line 22: The file imports the unused symbol `icons` from
'../shared/util/icons'; remove that unused import to clean up the code and
satisfy static analysis. Locate the import statement `import { icons } from
'../shared/util/icons';` in service-manager.ts and delete it (or remove `icons`
from the import list if multiple symbols are imported), then run lint/typecheck
to confirm the warning is resolved.

In `@apps/vscode/src/deploy/service-windows.ts`:
- Around line 248-271: The downloadFile function follows redirects recursively
with no depth limit; modify downloadFile(url: string, destPath: string,
maxRedirects = 10) to track remaining redirects and decrement on recursive
calls, and when response.headers.location is present call
this.downloadFile(newLocation, destPath, maxRedirects - 1) and reject with an
explicit error if maxRedirects <= 0; ensure the existing request/response
cleanup (response.destroy(), file.close()) and req.on('error') behavior remain
unchanged.

In `@apps/vscode/src/providers/PageDeployProvider.ts`:
- Line 365: The inline callback in PageDeployProvider.ts currently types the
response parameter as { statusCode?: number; on: Function; setEncoding: Function
}, which uses the unsafe Function type; change it to a proper shape or use
Node's IncomingMessage type from 'http' (or import IncomingMessage from 'http'
and type the callback param as res: IncomingMessage) and replace on: Function
and setEncoding: Function with their actual signatures (e.g., on(event: string,
listener: (...args: any[]) => void): this and setEncoding(encoding?: string):
void) so the https.get callback has explicit, type-safe method signatures.
- Around line 558-561: The private method getApiKey in PageDeployProvider is
unused and should be removed to clean up dead code; delete the entire
getApiKey() { const config = ConfigManager.getInstance().getConfig(); return
config.apiKey || ''; } method from the PageDeployProvider class (ensure no
references remain to getApiKey elsewhere and run a quick project-wide search for
getApiKey to confirm).

In `@apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx`:
- Around line 13-25: The ServiceStatus and DockerStatus interfaces in
PageDeploy.tsx intentionally duplicate types defined in service-manager.ts and
docker-manager.ts for webview isolation; add a short clear comment above both
interface declarations stating that this duplication is deliberate (webview runs
in a separate context) and should be kept in sync with the backend types to
prevent accidental drift, referencing the backend files by name
(service-manager.ts, docker-manager.ts) to guide future maintainers.
- Around line 97-100: The versionsLoading state is declared but never read;
either remove it and all calls to its setter (remove the useState hook for
versionsLoading and any calls to setVersionsLoading) or keep it and wire it into
the UI by showing a loading indicator in the version selector (e.g., disable the
version dropdown/split button and show a spinner when versionsLoading is true).
Specifically update the PageDeploy component: delete the const [versionsLoading,
setVersionsLoading] = useState(false) and remove setVersionsLoading(...) calls
if opting for removal, or if opting to use it, read versionsLoading where the
version dropdown or split button is rendered (referencing the versions,
selectedVersion, and setSelectedVersion state) to conditionally render a
spinner/disabled state while versions are loading.

In `@apps/vscode/src/providers/views/PageSettings/ConnectionSettings.tsx`:
- Around line 228-241: Replace the visual disabled separator option with
semantic optgroup(s) for the versions dropdown: wrap the static options
("latest", "prerelease") in one optgroup (e.g., label "Recommended") and the
dynamic engineVersions map in a separate optgroup (e.g., label "All versions"),
keeping the existing option elements created by engineVersions.map(v => <option
key={v.tag_name} value={v.tag_name}>{displayVersion(v.tag_name)}</option>), and
move the loading state option into the dynamic optgroup (or replace it with a
disabled optgroup label) so screen readers get proper grouping instead of a
disabled pseudo-separator.

In `@apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx`:
- Around line 32-53: INTEGRATIONS uses key: keyof SettingsData which may include
non-boolean fields; tighten the type by declaring a helper mapped type (e.g.,
BooleanKeys<T> = { [K in keyof T]: T[K] extends boolean ? K : never }[keyof T])
and change INTEGRATIONS to use key: BooleanKeys<SettingsData> so only boolean
integration keys (like integrationCopilot, integrationClaudeCode,
integrationCursor, integrationWindsurf) are allowed; update any related
references expecting keyof SettingsData to accept the narrowed
BooleanKeys<SettingsData> where needed.

In `@apps/vscode/src/providers/views/PageWelcome/styles.css`:
- Around line 95-158: Multiple selectors (.welcome-brand-sub, .welcome-tagline,
.welcome-features li, .welcome-feature-icon, .welcome-links a, .welcome-links
a:hover, .welcome-divider) repeat rgba(255,255,255,...) colors; define local CSS
custom properties on .welcome-left (e.g. --left-fg, --left-fg-strong,
--left-fg-muted, --left-divider) and replace the repeated rgba literals in those
classes with the new variables so future theme tweaks only change
.welcome-left's variable values.

In `@packages/ai/src/ai/modules/task/task_engine.py`:
- Line 46: The import list includes an unused symbol `debug` from `rocketlib` in
the statement that also imports `args as startup_args`; remove `debug` from that
import (leaving `args as startup_args`) to eliminate the unused import, or if
`debug` was intended to be used, add the necessary usage in this module (e.g.,
replace existing logging calls to use `debug`)—locate the import line with `from
rocketlib import debug, args as startup_args` and update it accordingly.

In `@packages/shared-ui/src/theme.ts`:
- Around line 327-338: The current MuiInputLabel override applies an italic,
light-weight, low-opacity style globally when inVSCode, reducing readability;
change the global override in the MuiInputLabel -> styleOverrides -> root block
to preserve readable defaults (use fontSize derived from baseFontSize, no forced
italic or reduced opacity), and move the compact/italic/light/low-opacity
presentation into a scoped alternative (e.g., a new CSS class or theme variant
such as "compactInputLabel" or a component override for specific components) so
only labelled compact inputs receive fontStyle: 'italic', fontWeight: 300 and
opacity: 0.7; reference the inVSCode flag to adjust only sizing (e.g., fontSize:
`${baseFontSize * 0.75}rem`) while removing global fontStyle/fontWeight/opacity
changes from MuiInputLabel.root.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b451f8c7-cd2b-4095-b409-78e3017f1488

📥 Commits

Reviewing files that changed from the base of the PR and between 6636ac5 and 1041310.

⛔ Files ignored due to path filters (2)
  • apps/vscode/onprem.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (33)
  • .vscode/launch.json
  • apps/vscode/esbuild.js
  • apps/vscode/package.json
  • apps/vscode/src/config.ts
  • apps/vscode/src/connection/connection.ts
  • apps/vscode/src/connection/engine-installer.ts
  • apps/vscode/src/connection/engine-manager.ts
  • apps/vscode/src/debugger/adapter.ts
  • apps/vscode/src/debugger/session.ts
  • apps/vscode/src/deploy/docker-manager.ts
  • apps/vscode/src/deploy/service-linux.ts
  • apps/vscode/src/deploy/service-mac.ts
  • apps/vscode/src/deploy/service-manager.ts
  • apps/vscode/src/deploy/service-windows.ts
  • apps/vscode/src/extension.ts
  • apps/vscode/src/providers/PageDeployProvider.ts
  • apps/vscode/src/providers/PageSettingsProvider.ts
  • apps/vscode/src/providers/PageStatusProvider.ts
  • apps/vscode/src/providers/SidebarFilesProvider.ts
  • apps/vscode/src/providers/styles/vscode.css
  • apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx
  • apps/vscode/src/providers/views/PageDeploy/styles.css
  • apps/vscode/src/providers/views/PageSettings/ConnectionSettings.tsx
  • apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx
  • apps/vscode/src/providers/views/PageSettings/PageSettings.tsx
  • apps/vscode/src/providers/views/PageSettings/styles.css
  • apps/vscode/src/providers/views/PageWelcome/PageWelcome.tsx
  • apps/vscode/src/providers/views/PageWelcome/styles.css
  • apps/vscode/src/stubs/ssh2.js
  • package.json
  • packages/ai/src/ai/modules/task/task_engine.py
  • packages/ai/src/ai/node.py
  • packages/shared-ui/src/theme.ts

Comment thread apps/vscode/src/config.ts
Comment thread apps/vscode/src/connection/engine-installer.ts
Comment thread apps/vscode/src/connection/engine-manager.ts
Comment thread apps/vscode/src/connection/engine-manager.ts Outdated
Comment thread apps/vscode/src/connection/engine-manager.ts Outdated
Comment thread apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx Outdated
Comment thread apps/vscode/src/providers/views/PageSettings/PageSettings.tsx Outdated
Comment thread apps/vscode/src/stubs/ssh2.js
Comment thread packages/ai/src/ai/modules/task/task_engine.py
Comment thread packages/ai/src/ai/node.py Outdated
@stepmikhaylov
stepmikhaylov marked this pull request as draft March 17, 2026 09:49
@stepmikhaylov
stepmikhaylov marked this pull request as draft March 17, 2026 09:49

@asclearuc asclearuc left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catches from coderabbit
Also, why also debug messages were removed

Comment thread packages/ai/src/ai/node.py Outdated
Comment thread packages/ai/src/ai/modules/task/task_engine.py
Rod-Christensen and others added 4 commits March 17, 2026 07:49
…t, function or class

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…t, function or class

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Comment thread apps/vscode/src/deploy/service-linux.ts Fixed
Comment thread apps/vscode/src/deploy/service-linux.ts Fixed
Comment thread apps/vscode/src/deploy/service-mac.ts Fixed
Comment thread apps/vscode/src/deploy/service-mac.ts Fixed
@Rod-Christensen Rod-Christensen changed the title Feat(vscode): Usability enhancements feat(vscode): Usability enhancements Mar 17, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/vscode/src/providers/views/PageSettings/PageSettings.tsx (1)

127-142: ⚠️ Potential issue | 🔴 Critical

Initial SettingsData state is missing required localDebugOutput.

SettingsData requires localDebugOutput, but the initializer omits it. This breaks type correctness and can surface as undefined UI state before settings load.

💡 Proposed fix
 	const [settings, setSettings] = useState<SettingsData>({
 		hostUrl: 'http://localhost:5565',
 		connectionMode: 'local',
 		hasApiKey: false,
 		apiKey: '', // Initialize empty - will be loaded from secure storage
 		autoConnect: true,
 		defaultPipelinePath: 'pipelines', // Initialize with default value
 		localEngineVersion: 'latest',
 		localEngineArgs: '',
+		localDebugOutput: false,
 		pipelineRestartBehavior: 'prompt',
 		envVars: {},
 		integrationCopilot: false,
 		integrationClaudeCode: false,
 		integrationCursor: false,
 		integrationWindsurf: false
 	});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/providers/views/PageSettings/PageSettings.tsx` around lines
127 - 142, The initial useState initializer for the settings state (const
[settings, setSettings] = useState<SettingsData>(...)) is missing the required
localDebugOutput property from the SettingsData type; update the initializer
object used in PageSettings to include a sensible default for localDebugOutput
(e.g., false or the appropriate default type/value) so the settings state is
fully typed and UI doesn't see undefined before loaded.
apps/vscode/src/connection/engine-manager.ts (2)

198-248: ⚠️ Potential issue | 🔴 Critical

Guard child-event handlers so stale process callbacks cannot clobber the active engine.

Current callbacks use shared instance state without verifying event ownership. If an older child emits error/exit after a new child starts, it can clear this.child, flip started, or kill the new process via cleanupProcess().

🐛 Minimal fix
-			this.child = spawn(executablePath, args, {
+			const child = spawn(executablePath, args, {
 				cwd: path.dirname(executablePath),
 				stdio: 'pipe',
 			});
+			this.child = child;

@@
-			const myPidFile = this.child.pid
-				? path.join(path.dirname(executablePath), `engine-${this.child.pid}.pid`)
+			const myPidFile = child.pid
+				? path.join(path.dirname(executablePath), `engine-${child.pid}.pid`)
 				: undefined;

@@
-					fs.writeFileSync(myPidFile, String(this.child.pid));
+					fs.writeFileSync(myPidFile, String(child.pid));
 				} catch {
@@
-			this.child.on('error', (err) => {
+			child.on('error', (err) => {
+				if (this.child !== child) return;
 				if (!processReady && !processErrored) {
 					processErrored = true;
 					this.logger.output(`${icons.error} DAP server failed to launch: ${err.message}`);
 					this.cleanupProcess(myPidFile);
 					reject(err);
 				}
 			});

-			this.child.on('exit', (code, signal) => {
+			child.on('exit', (code, signal) => {
 				// Clean up PID file on exit — only if it's still ours
 				this.removePidFile(myPidFile);
+				if (this.child !== child) return;

 				if (!processReady && !processErrored) {
 					processErrored = true;
 					this.logger.output(`${icons.error} DAP server exited during startup (code=${code}, signal=${signal})`);
 					this.cleanupProcess();
 					reject(new Error(`Process exited during startup: code=${code}, signal=${signal}`));
 					return;
 				}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/connection/engine-manager.ts` around lines 198 - 248, The
child process event handlers currently act on shared instance state and can be
triggered by stale children; fix by capturing the spawned child and its pid-file
into local constants (e.g. const launched = this.child and const myPidFile as
already set) and at the top of each handler (the functions attached to
child.on('error') and child.on('exit')) verify the event belongs to that
launched instance (if (this.child !== launched) return) before performing any
cleanup, writing logs, flipping started, clearing this.child, calling
cleanupProcess()/removePidFile(), or emitting 'terminated' so stale callbacks
cannot clobber a new engine.

266-291: ⚠️ Potential issue | 🟠 Major

Make readiness parsing line-buffered across stream chunks.

stdout/stderr chunks are arbitrary; parsing each chunk independently can split the Uvicorn running... line and never resolve startup.

🔧 Suggested hardening
+			let stdoutBuffer = '';
+			let stderrBuffer = '';
+
+			const processOutputChunk = (chunk: string, stream: 'stdout' | 'stderr'): void => {
+				let buffer = (stream === 'stdout' ? stdoutBuffer : stderrBuffer) + chunk;
+				const lines = buffer.split(/\r?\n/);
+				buffer = lines.pop() ?? '';
+
+				for (const raw of lines) {
+					const msg = raw.trim();
+					if (!msg) continue;
+					this.logger.console(msg);
+					if (msg.includes('Uvicorn running')) {
+						tryResolveReady(msg);
+					}
+				}
+
+				if (stream === 'stdout') stdoutBuffer = buffer;
+				else stderrBuffer = buffer;
+			};
+
 			// Monitor stdout for readiness
 			this.child.stdout?.on('data', (data) => {
-				const output = data.toString();
-				for (const message of output.split('\n')) {
-					const msg = message.trim();
-					if (msg) {
-						this.logger.console(msg);
-						if (msg.includes('Uvicorn running')) {
-							tryResolveReady(msg);
-						}
-					}
-				}
+				processOutputChunk(data.toString(), 'stdout');
 			});

 			// Monitor stderr
 			this.child.stderr?.on('data', (data) => {
-				const output = data.toString();
-				for (const message of output.split('\n')) {
-					const msg = message.trim();
-					if (msg) {
-						this.logger.console(msg);
-						if (msg.includes('Uvicorn running')) {
-							tryResolveReady(msg);
-						}
-					}
-				}
+				processOutputChunk(data.toString(), 'stderr');
 			});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/connection/engine-manager.ts` around lines 266 - 291, The
stdout/stderr handlers currently parse each incoming chunk independently which
can split the "Uvicorn running" line across chunks; change the handlers on
this.child.stdout?.on('data', ...) and this.child.stderr?.on('data', ...) to use
a line-buffered approach: keep a per-stream accumulator string, append each
chunk, split on '\n' and process only the complete lines (trim and call
this.logger.console and tryResolveReady when a line includes 'Uvicorn running'),
and preserve the final partial segment back into the accumulator for the next
chunk so partial lines are not lost.
🧹 Nitpick comments (3)
apps/vscode/src/providers/views/PageWelcome/styles.css (1)

156-165: Minor inconsistency: hover color is hardcoded while base color uses variable.

The base link color uses var(--left-fg), but the hover state hardcodes #ffffff. For consistency, consider adding a --left-fg-hover custom property or reusing --left-fg-strong.

♻️ Suggested consistency improvement
 .welcome-links a:hover {
-	color: `#ffffff`;
+	color: var(--left-fg-strong);
 	text-decoration: underline;
 }

Or define a dedicated hover variable with the other custom properties:

--left-fg-hover: `#ffffff`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/providers/views/PageWelcome/styles.css` around lines 156 -
165, The hover rule for .welcome-links a hardcodes `#ffffff` while the base uses
var(--left-fg); change the hover to use a CSS variable (e.g.,
var(--left-fg-hover) or var(--left-fg-strong)) and add that custom property
alongside the other theme vars so hover styling stays consistent and themable;
update the .welcome-links a:hover selector to reference the chosen variable
instead of the literal color.
apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx (1)

502-503: Move module constant to top of file for clarity.

IMAGE_BASE is defined at the end of the file but used in the component JSX (line 398). While this works due to module-level hoisting, placing constants at the top improves readability and follows common conventions.

♻️ Suggested refactor

Move line 503 to after the imports (around line 12):

 import './styles.css';
+
+const IMAGE_BASE = 'ghcr.io/rocketride-org/rocketride-engine';

 // These interfaces are intentionally duplicated...

And remove lines 502-503.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx` around lines 502 -
503, IMAGE_BASE is declared near the end of the module but is referenced earlier
in the PageDeploy component JSX; move the module constant IMAGE_BASE to the top
of the file just after the import block (so it appears before the PageDeploy
component and its JSX), removing the original trailing declaration, to improve
readability and follow convention.
apps/vscode/src/deploy/service-windows.ts (1)

204-246: Consider adding integrity verification for NSSM download.

The NSSM binary is downloaded over HTTPS, which provides transport security, but there's no verification of the downloaded archive's integrity. A compromised or man-in-the-middle attack could potentially inject malicious code.

Consider adding a SHA-256 hash verification after download:

🛡️ Optional: Add hash verification
+import * as crypto from 'crypto';
+
+const NSSM_SHA256 = 'expected_sha256_hash_here';
+
 private async downloadNssm(): Promise<void> {
 	// ... download logic ...
 	await this.downloadFile(NSSM_DOWNLOAD_URL, zipPath);
+	
+	// Verify integrity
+	const hash = crypto.createHash('sha256');
+	hash.update(fs.readFileSync(zipPath));
+	const actualHash = hash.digest('hex');
+	if (actualHash !== NSSM_SHA256) {
+		fs.unlinkSync(zipPath);
+		throw new Error('NSSM integrity check failed');
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/deploy/service-windows.ts` around lines 204 - 246, Add
SHA-256 integrity verification in downloadNssm: after the retry loop where
zipPath is written and before extracting with AdmZip, compute the SHA-256 of the
downloaded file (using Node's crypto on fs.readFileSync(zipPath)) and compare it
to an expected constant (e.g., NSSM_ZIP_SHA256) that you add near
NSSM_DOWNLOAD_URL; if the digest does not match, delete the zip and throw an
Error indicating checksum mismatch. Keep all existing retry/cleanup behavior
(use downloadFile, NSSM_DIR, NSSM_PATH) and fail fast before zip.extractEntryTo
if the hash check fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/vscode/src/config.ts`:
- Around line 499-501: getApiHost currently returns config.hostUrl even in local
mode which can write a stale remote URI into .env; update getApiHost to detect
local mode (the runtime mode used by ConnectionManager/dynamic port) and, when
local, ignore config.hostUrl and return the local fallback
'http://localhost:5565' instead, otherwise keep the existing behavior using
config.hostUrl || 'http://localhost:5565'. Ensure the check is placed in
getApiHost so .env sync only persists the real remote URL when not in local
mode.

In `@apps/vscode/src/connection/engine-installer.ts`:
- Around line 214-219: The uninstall() method deletes enginesRoot without using
the cross-process installer lock; update uninstall() to acquire the same
installer lock used elsewhere in the EngineInstaller (or engine installer
module) before checking/deleting this.enginesRoot and to release the lock when
finished (including on errors), so deletion is serialized with concurrent
installs/updates—locate and reuse the existing lock acquisition/release
functions (the same lock helpers used by install/update paths) around the
fs.existsSync / fs.rmSync operations inside uninstall().

In `@apps/vscode/src/providers/PageDeployProvider.ts`:
- Around line 524-553: DOCKER_CONFIG_PATH currently points to a global location
that on Linux may be unwritable by a normal user; update the definition of
DOCKER_CONFIG_PATH and any reads/writes (referencing DOCKER_CONFIG_PATH,
writeDockerConfig, readDockerConfig, deleteDockerConfig) to use a per-user,
writable config location instead (for example prefer process.env.XDG_CONFIG_HOME
if set, otherwise fall back to the user's home config directory such as
path.join(os.homedir(), '.config', 'RocketRide', 'docker-config.json')), then
keep the existing mkdirSync/writeFileSync/readFileSync/unlinkSync logic but
operating on that user-writable path so no root permissions are required. Ensure
the new path construction uses path.join and preserves the RocketRide
subdirectory naming.
- Around line 385-397: The CONFIG_PATH currently resolves to /etc/... on Linux
which is not writable by normal users; update PageDeployProvider.CONFIG_PATH to
pick a user-writable location on Linux (prefer XDG_CONFIG_HOME or fallback to
path.join(os.homedir(), '.config', 'RocketRide', 'config.json')) and keep the
existing Windows/macOS behavior for other platforms, then ensure
writeServiceConfig still uses that constant (PageDeployProvider.CONFIG_PATH) and
creates the directory with fs.mkdirSync(..., { recursive: true }) before
writing; alternatively, if you intend system-wide config, document that
installation must create /etc/RocketRide with correct permissions and only use
that path during an elevated installer path resolution.

In `@packages/ai/src/ai/modules/task/task_engine.py`:
- Line 1459: The warning path calls logger.warning(...) but no module-level
logger is defined, causing a NameError; fix by ensuring a logger exists and is
used—either import logging and call logging.getLogger(__name__) once at module
scope (e.g., define logger = logging.getLogger(__name__)) and then keep
logger.warning(f"Failed to parse engine arg {arg!r}: {e}, using as-is"), or
replace the call with logging.warning(...) so the malformed-arg handling doesn't
crash; update the reference to the symbol logger.warning in task_engine.py
accordingly.

---

Outside diff comments:
In `@apps/vscode/src/connection/engine-manager.ts`:
- Around line 198-248: The child process event handlers currently act on shared
instance state and can be triggered by stale children; fix by capturing the
spawned child and its pid-file into local constants (e.g. const launched =
this.child and const myPidFile as already set) and at the top of each handler
(the functions attached to child.on('error') and child.on('exit')) verify the
event belongs to that launched instance (if (this.child !== launched) return)
before performing any cleanup, writing logs, flipping started, clearing
this.child, calling cleanupProcess()/removePidFile(), or emitting 'terminated'
so stale callbacks cannot clobber a new engine.
- Around line 266-291: The stdout/stderr handlers currently parse each incoming
chunk independently which can split the "Uvicorn running" line across chunks;
change the handlers on this.child.stdout?.on('data', ...) and
this.child.stderr?.on('data', ...) to use a line-buffered approach: keep a
per-stream accumulator string, append each chunk, split on '\n' and process only
the complete lines (trim and call this.logger.console and tryResolveReady when a
line includes 'Uvicorn running'), and preserve the final partial segment back
into the accumulator for the next chunk so partial lines are not lost.

In `@apps/vscode/src/providers/views/PageSettings/PageSettings.tsx`:
- Around line 127-142: The initial useState initializer for the settings state
(const [settings, setSettings] = useState<SettingsData>(...)) is missing the
required localDebugOutput property from the SettingsData type; update the
initializer object used in PageSettings to include a sensible default for
localDebugOutput (e.g., false or the appropriate default type/value) so the
settings state is fully typed and UI doesn't see undefined before loaded.

---

Nitpick comments:
In `@apps/vscode/src/deploy/service-windows.ts`:
- Around line 204-246: Add SHA-256 integrity verification in downloadNssm: after
the retry loop where zipPath is written and before extracting with AdmZip,
compute the SHA-256 of the downloaded file (using Node's crypto on
fs.readFileSync(zipPath)) and compare it to an expected constant (e.g.,
NSSM_ZIP_SHA256) that you add near NSSM_DOWNLOAD_URL; if the digest does not
match, delete the zip and throw an Error indicating checksum mismatch. Keep all
existing retry/cleanup behavior (use downloadFile, NSSM_DIR, NSSM_PATH) and fail
fast before zip.extractEntryTo if the hash check fails.

In `@apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx`:
- Around line 502-503: IMAGE_BASE is declared near the end of the module but is
referenced earlier in the PageDeploy component JSX; move the module constant
IMAGE_BASE to the top of the file just after the import block (so it appears
before the PageDeploy component and its JSX), removing the original trailing
declaration, to improve readability and follow convention.

In `@apps/vscode/src/providers/views/PageWelcome/styles.css`:
- Around line 156-165: The hover rule for .welcome-links a hardcodes `#ffffff`
while the base uses var(--left-fg); change the hover to use a CSS variable
(e.g., var(--left-fg-hover) or var(--left-fg-strong)) and add that custom
property alongside the other theme vars so hover styling stays consistent and
themable; update the .welcome-links a:hover selector to reference the chosen
variable instead of the literal color.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6dda088b-03bf-4c3a-8778-532ca76725c8

📥 Commits

Reviewing files that changed from the base of the PR and between 1041310 and e79263a.

📒 Files selected for processing (18)
  • apps/vscode/src/config.ts
  • apps/vscode/src/connection/engine-installer.ts
  • apps/vscode/src/connection/engine-manager.ts
  • apps/vscode/src/deploy/docker-manager.ts
  • apps/vscode/src/deploy/service-linux.ts
  • apps/vscode/src/deploy/service-mac.ts
  • apps/vscode/src/deploy/service-manager.ts
  • apps/vscode/src/deploy/service-windows.ts
  • apps/vscode/src/providers/PageDeployProvider.ts
  • apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx
  • apps/vscode/src/providers/views/PageSettings/ConnectionSettings.tsx
  • apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx
  • apps/vscode/src/providers/views/PageSettings/PageSettings.tsx
  • apps/vscode/src/providers/views/PageWelcome/styles.css
  • apps/vscode/src/stubs/ssh2.js
  • packages/ai/src/ai/modules/task/task_engine.py
  • packages/ai/src/ai/node.py
  • packages/shared-ui/src/theme.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/vscode/src/deploy/service-manager.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/shared-ui/src/theme.ts
  • apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx
  • packages/ai/src/ai/node.py

Comment thread apps/vscode/src/config.ts Outdated
Comment thread apps/vscode/src/connection/engine-installer.ts Outdated
Comment thread apps/vscode/src/providers/PageDeployProvider.ts
Comment thread apps/vscode/src/providers/PageDeployProvider.ts
Comment thread packages/ai/src/ai/modules/task/task_engine.py Outdated
@github-actions github-actions Bot added module:vscode VS Code extension module:ai AI/ML modules module:ui Chat UI and Dropper UI labels Mar 18, 2026
@Rod-Christensen
Rod-Christensen marked this pull request as ready for review March 18, 2026 05:16

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

♻️ Duplicate comments (4)
packages/ai/src/ai/modules/task/task_engine.py (1)

1459-1462: ⚠️ Potential issue | 🔴 Critical

Undefined logger will crash on malformed engine arguments.

logger is not defined in this module. When shlex.split() raises ValueError on malformed input (e.g., unclosed quotes), this line will raise NameError, masking the original parsing issue. The class already provides self.debug_message() for logging.

🐛 Proposed fix
                     except ValueError as e:
-                        logger.warning(f"Failed to parse engine arg {arg!r}: {e}, using as-is")
+                        self.debug_message(f"Failed to parse engine arg {arg!r}: {e}, using as-is")
                         child_args.append(arg)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ai/src/ai/modules/task/task_engine.py` around lines 1459 - 1462, The
except block catching ValueError after child_args.extend(shlex.split(arg)) uses
an undefined logger and will raise NameError; replace the logger.warning call
with the class logger helper by calling self.debug_message(...) (or the
appropriate instance logging method provided) and include the arg and exception
details in the message, then append the original arg to child_args as before so
malformed args are used as-is; update the except block around shlex.split(arg)
in the code path that builds child_args accordingly.
apps/vscode/src/config.ts (1)

499-501: ⚠️ Potential issue | 🟡 Minor

Local mode may sync a stale remote URL to .env.

In local mode, getApiHost() still returns config.hostUrl when present. If a user previously used on-prem mode, the .env sync could persist that remote URI instead of a consistent local default. Since the actual URL is determined at runtime by ConnectionManager, consider always returning the local fallback in local mode for .env consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/config.ts` around lines 499 - 501, getApiHost currently
returns config.hostUrl when present, which can persist a stale remote URI into
.env; update getApiHost so that when the extension is running in local mode (use
the existing local-mode flag/utility used elsewhere in this module — e.g., the
local mode boolean or isLocalMode()/isLocal flag), it always returns the local
fallback string 'http://localhost:5565' (ignore config.hostUrl) so .env sync is
consistent; keep ConnectionManager behavior unchanged for runtime resolution.
apps/vscode/src/connection/engine-installer.ts (1)

214-219: ⚠️ Potential issue | 🟠 Major

uninstall() lacks cross-process lock coordination.

Unlike install() and cleanupOldVersions(), uninstall() deletes the entire enginesRoot without acquiring the cross-process lock. A concurrent VS Code window could be installing or updating pointers while this runs.

🔒 Proposed fix
-	public uninstall(): void {
+	public async uninstall(): Promise<void> {
+		fs.mkdirSync(this.enginesRoot, { recursive: true });
+		this.ensureLockFileExists();
+		let release: (() => Promise<void>) | undefined;
+		try {
+			release = await lockfile.lock(this.lockFilePath(), {
+				stale: 120000,
+				retries: { retries: 10, minTimeout: 500, maxTimeout: 3000 },
+			});
+		} catch {
+			this.logger.output(`${icons.warning} Could not acquire lock for uninstall`);
+			return;
+		}
+		try {
 			if (fs.existsSync(this.enginesRoot)) {
 				fs.rmSync(this.enginesRoot, { recursive: true, force: true });
 				this.logger.output(`${icons.info} All engines uninstalled from ${this.enginesRoot}`);
 			}
+		} finally {
+			if (release) await release();
+		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/connection/engine-installer.ts` around lines 214 - 219, The
uninstall() method deletes enginesRoot without using the same cross-process lock
used by install() and cleanupOldVersions(), risking races; modify uninstall() to
acquire the same cross-process lock (the lock mechanism used by
install()/cleanupOldVersions()), perform the exists check and rmSync(...) while
holding the lock, and always release the lock in a finally block (with a
reasonable timeout/try-acquire failure path) so concurrent VS Code windows
cannot install/update pointers while enginesRoot is removed.
apps/vscode/src/deploy/service-linux.ts (1)

69-73: ⚠️ Potential issue | 🔴 Critical

Use secure temp files for privileged unit updates.

Line 69 and Line 98 use a fixed filename in /tmp (rocketride.service.tmp) before sudo cp. This is vulnerable to symlink/race attacks and can let another local user swap unit contents before root installs them.

🔒 Proposed hardening diff
+	private writeSecureTempUnit(unitContent: string): { tmpDir: string; tmpUnit: string } {
+		const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rocketride-'));
+		const tmpUnit = path.join(tmpDir, 'rocketride.service');
+		fs.writeFileSync(tmpUnit, unitContent, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
+		return { tmpDir, tmpUnit };
+	}
+
 	public async install(executablePath: string, engineDir: string): Promise<void> {
 		// Create install root with elevation (may not be user-writable)
 		await this.runSudo('mkdir', ['-p', INSTALL_ROOT]);

 		const unitContent = this.buildUnitFile(executablePath, engineDir);
-		const tmpUnit = path.join(os.tmpdir(), 'rocketride.service.tmp');
-		fs.writeFileSync(tmpUnit, unitContent, 'utf8');
-
-		await this.runSudo('cp', [tmpUnit, UNIT_PATH]);
-		fs.unlinkSync(tmpUnit);
+		const { tmpDir, tmpUnit } = this.writeSecureTempUnit(unitContent);
+		try {
+			await this.runSudo('cp', [tmpUnit, UNIT_PATH]);
+		} finally {
+			fs.rmSync(tmpDir, { recursive: true, force: true });
+		}

 		await this.runSudo('systemctl', ['daemon-reload']);
 		await this.runSudo('systemctl', ['enable', UNIT_NAME]);
 		await this.runSudo('systemctl', ['start', UNIT_NAME]);
@@
 	public async update(executablePath: string, engineDir: string): Promise<void> {
 		await this.runSudo('systemctl', ['stop', UNIT_NAME]);

 		const unitContent = this.buildUnitFile(executablePath, engineDir);
-		const tmpUnit = path.join(os.tmpdir(), 'rocketride.service.tmp');
-		fs.writeFileSync(tmpUnit, unitContent, 'utf8');
-		await this.runSudo('cp', [tmpUnit, UNIT_PATH]);
-		fs.unlinkSync(tmpUnit);
+		const { tmpDir, tmpUnit } = this.writeSecureTempUnit(unitContent);
+		try {
+			await this.runSudo('cp', [tmpUnit, UNIT_PATH]);
+		} finally {
+			fs.rmSync(tmpDir, { recursive: true, force: true });
+		}

 		await this.runSudo('systemctl', ['daemon-reload']);
 		await this.runSudo('systemctl', ['start', UNIT_NAME]);
#!/bin/bash
# Verify predictable temp unit usage in privileged copy paths.
rg -n -C2 "rocketride\\.service\\.tmp|writeFileSync\\(tmpUnit|runSudo\\('cp', \\[tmpUnit, UNIT_PATH\\]\\)"

Also applies to: 98-101

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/deploy/service-linux.ts` around lines 69 - 73, The code uses
a predictable temp file name (tmpUnit = path.join(os.tmpdir(),
'rocketride.service.tmp')) and then writes with fs.writeFileSync and later runs
runSudo('cp', [tmpUnit, UNIT_PATH]) which is susceptible to symlink/race
attacks; change to create a secure, unique temp file (e.g., use fs.mkdtemp or a
UUID-based filename or tmp.file from a secure temp library) and write
unitContent there, verify the file is created with safe permissions (0600)
before invoking runSudo('cp', [secureTmpPath, UNIT_PATH]), and ensure you unlink
the same secure path after copy; update all occurrences (tmpUnit,
fs.writeFileSync, fs.unlinkSync, runSudo('cp', [tmpUnit, UNIT_PATH]))
accordingly.
🧹 Nitpick comments (4)
apps/vscode/src/providers/views/PageWelcome/styles.css (1)

162-165: Add keyboard-visible focus style for left-panel links.

Hover styling was updated, but keyboard users should get an equivalent visual cue on focus.

♿ Suggested accessibility refinement
-.welcome-links a:hover {
+.welcome-links a:hover,
+.welcome-links a:focus-visible {
 	color: `#ffffff`;
 	text-decoration: underline;
+	outline: 1px solid var(--left-fg-strong);
+	outline-offset: 2px;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/providers/views/PageWelcome/styles.css` around lines 162 -
165, The hover-only rule for left-panel links (.welcome-links a:hover) lacks a
keyboard-visible focus style, so add matching focus styles for keyboard users by
extending the selector to include :focus and preferably :focus-visible (e.g.,
.welcome-links a:focus, .welcome-links a:focus-visible) and apply the same color
and underline (or an accessible alternative) to ensure keyboard users receive an
equivalent visual cue; update the existing .welcome-links a:hover declaration to
include these focus selectors and ensure it meets contrast and visibility
requirements for the left-panel links.
apps/vscode/src/deploy/service-mac.ts (1)

132-164: Consider escaping XML special characters in interpolated values.

The template interpolates executablePath, workingDir, etc. directly into XML. While these are typically system paths controlled by the extension, paths containing &, <, or > would produce malformed plist XML.

A simple helper would make this more robust:

♻️ Suggested helper
+private escapeXml(s: string): string {
+	return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+}
+
 private buildPlist(executablePath: string, workingDir: string): string {
+	const exe = this.escapeXml(executablePath);
+	const wd = this.escapeXml(workingDir);
+	const logs = this.escapeXml(LOGS_DIR);
 	return `<?xml version="1.0" encoding="UTF-8"?>
 ...
-		<string>${executablePath}</string>
+		<string>${exe}</string>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/deploy/service-mac.ts` around lines 132 - 164, The buildPlist
function interpolates values like PLIST_LABEL, executablePath, workingDir,
SERVICE_PORT and path.join(LOGS_DIR, ...) directly into XML which can break the
plist if any value contains XML-special characters; add a small helper (e.g.,
escapeXml) to replace &, <, >, " and ' with their entities and call it for every
interpolated value used in buildPlist (PLIST_LABEL, executablePath, workingDir,
SERVICE_PORT string, and the stdout/stderr paths produced via path.join) before
embedding them into the template.
apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx (1)

71-79: Associate help text with each checkbox for assistive tech.

At Line 79, the description is not programmatically linked to the checkbox, so screen readers may miss it during focus navigation.

♿ Suggested accessibility patch
-						{INTEGRATIONS.map(({ key, label, description }) => (
-							<React.Fragment key={key}>
+						{INTEGRATIONS.map(({ key, label, description }) => {
+							const helpTextId = `${key}-help-text`;
+							return (
+							<React.Fragment key={key}>
 								<label>
 									<input
 										type="checkbox"
 										checked={!!settings[key]}
+										aria-describedby={helpTextId}
 										onChange={(e) => onSettingsChange({ [key]: e.target.checked })}
 									/>
 									<span>{label}</span>
 								</label>
-								<div className="help-text">{description}</div>
+								<div id={helpTextId} className="help-text">{description}</div>
 							</React.Fragment>
-						))}
+							);
+						})}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx` around
lines 71 - 79, The checkbox input in IntegrationSettings (the input using
checked={!!settings[key]} and onChange calling onSettingsChange) is not
programmatically linked to its help text (description), so add an accessible
relationship by giving the help text element a unique id (e.g. `${key}-help`)
and adding aria-describedby referring to that id on the input; ensure the id is
stable/unique per key and keep the help text element (the div with
className="help-text") targeted by aria-describedby so screen readers announce
the description when the checkbox receives focus.
apps/vscode/package.json (1)

94-98: Sync apps/vscode/docs/api/ with the new public manifest surface before release.

This PR introduces public settings/commands (rocketride.local.debugOutput, rocketride.integrations.*, focus commands, and command discoverability changes). Please update the API docs set so extension behavior and supported config are documented as a stable contract.

Based on learnings: Reference and quote relevant documentation from apps/vscode/docs/api/ in code review responses when discussing Rocket Ride code.

Also applies to: 130-149, 162-171, 284-313

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/package.json` around lines 94 - 98, Update the API docs in
apps/vscode/docs/api to reflect the new public manifest surface: add entries for
the new settings (rocketride.local.debugOutput with its
boolean/default/description, all rocketride.integrations.* keys), the new focus
commands and any command discoverability changes, and ensure descriptions,
defaults and visibility match the package.json manifest; also update example
usage and a stability/compatibility note so this is presented as a stable
contract, and after editing, reference/quote the updated docs when discussing
Rocket Ride code in future reviews.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/vscode/src/deploy/service-linux.ts`:
- Line 56: The child process created by runSudo is leaving stdout undrained,
which can block on verbose commands like apt-get install (invoked with
LinuxServiceManager.ENGINE_DEPS); update the runSudo implementation used by
service-linux.ts (and similarly service-mac.ts) to attach a 'data' listener on
child.stdout (e.g., child.stdout.on('data', ()=>{})) to continuously drain
stdout, and ensure the listener is added before awaiting process completion so
the stdout buffer cannot fill and stall the deployment flow.

In `@apps/vscode/src/deploy/service-windows.ts`:
- Around line 204-245: The downloadNssm method currently downloads and extracts
nssm.exe without validating its integrity or signature; update it to verify the
payload before extraction/execution by either (a) pinning a known SHA-256 digest
and computing the downloaded zip/file hash via the downloadFile result and
comparing to the constant, or (b) validating the Authenticode signature of the
extracted nssm.exe using a Windows signature check library, and fail the
operation if validation fails; locate the logic in downloadNssm (and where
downloadFile is used) and add the hash/signature verification step against a
stored constant (e.g. NSSM_SHA256 or an Authenticode check) before
zip.extractEntryTo and before any elevated execution of NSSM_PATH, logging and
throwing a descriptive error on mismatch.
- Around line 72-83: Update the NSSM install/start command arguments so the
engine is bound to loopback instead of all interfaces: in the runElevatedScript
call that builds psCmd(...) for NSSM (references: runElevatedScript, psCmd,
NSSM_PATH, SERVICE_NAME, SERVICE_PORT) replace the `--host=0.0.0.0` argument
with `--host=127.0.0.1`; make the same change in the other similar NSSM psCmd
block later in the file (the second install/start sequence).
- Around line 114-121: The current catch on the runElevatedScript('remove.ps1',
script) call swallows all errors; change it to inspect the thrown error from
runElevatedScript (or its message) and only ignore the explicit "service not
installed" condition, while rethrowing or logging and aborting for other errors
(e.g., UAC cancellation, missing nssm.exe, removal failure). Only proceed to
remove INSTALL_ROOT and call this.logger.output(`${icons.success} Service
removed`) when the elevated script actually succeeded; if the script fails with
any non-"not installed" error, surface that error (reject/throw or log and
return) instead of continuing. Locate this logic around
runElevatedScript/remove.ps1, INSTALL_ROOT cleanup, and the success log to
implement the conditional error handling.
- Around line 289-299: The PowerShell wrapper (psCommand) uses Start-Process
-Wait which doesn't propagate the elevated process exit code back to the caller,
so execFile sees success even when the elevated script fails; change psCommand
to start the elevated process with -PassThru, capture the returned process
object, and explicitly exit with its ExitCode (e.g. $p = Start-Process ... -Wait
-PassThru; exit $p.ExitCode) so execFile('powershell.exe', ...) receives the
real non-zero exit code and the existing error handling in the execFile callback
will reject appropriately.

In `@apps/vscode/src/providers/PageDeployProvider.ts`:
- Around line 324-340: The wait loops (waitForServiceRunning and the similar
loop at lines 568-583) currently only log a timeout and allow flows to continue;
change them to fail the action by throwing an Error (or rejecting) when the
timeout elapses so upstream handlers like connectToService, serviceComplete, and
dockerComplete do not run on failed startups. Locate the methods
waitForServiceRunning and the analogous wait loop, replace the final logger-only
path with a thrown Error containing clear context (e.g., include timeoutSec and
service id/name), and ensure callers either await/propagate the thrown Error or
handle it to abort the deployment flow.
- Around line 483-491: getDockerImageTag currently returns the literal 'latest',
which causes dockerInstall to persist 'latest' and dockerUpdate to skip pulling;
change the behavior so 'latest' is resolved to a concrete image identifier (tag
or digest) before persisting or ensure update logic always pulls for the latest
channel. Specifically, in getDockerImageTag and the calling flow
(dockerInstall/dockerUpdate) detect when versionSpec === 'latest' and (a) query
GHCR or the ghcrTags list to find the actual latest stable tag or digest and
return that concrete value, or (b) if resolution is not done here, update
dockerUpdate to treat the saved value specially when the selected channel is
'latest' by forcing a pull regardless of equality. Apply the same change to the
other occurrences noted around lines 493-500 and 517-524 so 'latest' is never
stored/treated as already up-to-date.

In `@apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx`:
- Around line 242-259: The split-button toggle and options are not
keyboard/screen-reader accessible; update the toggle button used in PageDeploy
to include an accessible name (aria-label), aria-expanded bound to isOpen, and
aria-haspopup/aria-controls referencing the dropdown id, and convert each option
rendered in the options.map (currently a clickable div) into keyboard-focusable,
semantic controls (either <button> or elements with role="menuitem" and
tabIndex={0}) and add onKeyDown handlers to those items to trigger
onVersionChange on Enter/Space and close the menu via setDropdownOpen(null) on
selection or Escape; also ensure the dropdown container (split-button-dropdown)
has an appropriate role (menu) and that focus is moved into the menu when
opening (e.g., focus the selected item) so keyboard users can navigate.

In `@apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx`:
- Around line 27-55: The docs are missing entries for the new boolean settings;
update the Extension Settings documentation table in README-vscode.md to add
rows for integrationCopilot, integrationClaudeCode, integrationCursor, and
integrationWindsurf (use the exact key names), each with a short description
like "Enable RocketRide integration with GitHub Copilot" (and analogous
descriptions for Claude Code, Cursor, and Windsurf) and the appropriate type
(boolean) and default value; ensure the new rows follow the existing table
column order/format so they render consistently.

---

Duplicate comments:
In `@apps/vscode/src/config.ts`:
- Around line 499-501: getApiHost currently returns config.hostUrl when present,
which can persist a stale remote URI into .env; update getApiHost so that when
the extension is running in local mode (use the existing local-mode flag/utility
used elsewhere in this module — e.g., the local mode boolean or
isLocalMode()/isLocal flag), it always returns the local fallback string
'http://localhost:5565' (ignore config.hostUrl) so .env sync is consistent; keep
ConnectionManager behavior unchanged for runtime resolution.

In `@apps/vscode/src/connection/engine-installer.ts`:
- Around line 214-219: The uninstall() method deletes enginesRoot without using
the same cross-process lock used by install() and cleanupOldVersions(), risking
races; modify uninstall() to acquire the same cross-process lock (the lock
mechanism used by install()/cleanupOldVersions()), perform the exists check and
rmSync(...) while holding the lock, and always release the lock in a finally
block (with a reasonable timeout/try-acquire failure path) so concurrent VS Code
windows cannot install/update pointers while enginesRoot is removed.

In `@apps/vscode/src/deploy/service-linux.ts`:
- Around line 69-73: The code uses a predictable temp file name (tmpUnit =
path.join(os.tmpdir(), 'rocketride.service.tmp')) and then writes with
fs.writeFileSync and later runs runSudo('cp', [tmpUnit, UNIT_PATH]) which is
susceptible to symlink/race attacks; change to create a secure, unique temp file
(e.g., use fs.mkdtemp or a UUID-based filename or tmp.file from a secure temp
library) and write unitContent there, verify the file is created with safe
permissions (0600) before invoking runSudo('cp', [secureTmpPath, UNIT_PATH]),
and ensure you unlink the same secure path after copy; update all occurrences
(tmpUnit, fs.writeFileSync, fs.unlinkSync, runSudo('cp', [tmpUnit, UNIT_PATH]))
accordingly.

In `@packages/ai/src/ai/modules/task/task_engine.py`:
- Around line 1459-1462: The except block catching ValueError after
child_args.extend(shlex.split(arg)) uses an undefined logger and will raise
NameError; replace the logger.warning call with the class logger helper by
calling self.debug_message(...) (or the appropriate instance logging method
provided) and include the arg and exception details in the message, then append
the original arg to child_args as before so malformed args are used as-is;
update the except block around shlex.split(arg) in the code path that builds
child_args accordingly.

---

Nitpick comments:
In `@apps/vscode/package.json`:
- Around line 94-98: Update the API docs in apps/vscode/docs/api to reflect the
new public manifest surface: add entries for the new settings
(rocketride.local.debugOutput with its boolean/default/description, all
rocketride.integrations.* keys), the new focus commands and any command
discoverability changes, and ensure descriptions, defaults and visibility match
the package.json manifest; also update example usage and a
stability/compatibility note so this is presented as a stable contract, and
after editing, reference/quote the updated docs when discussing Rocket Ride code
in future reviews.

In `@apps/vscode/src/deploy/service-mac.ts`:
- Around line 132-164: The buildPlist function interpolates values like
PLIST_LABEL, executablePath, workingDir, SERVICE_PORT and path.join(LOGS_DIR,
...) directly into XML which can break the plist if any value contains
XML-special characters; add a small helper (e.g., escapeXml) to replace &, <, >,
" and ' with their entities and call it for every interpolated value used in
buildPlist (PLIST_LABEL, executablePath, workingDir, SERVICE_PORT string, and
the stdout/stderr paths produced via path.join) before embedding them into the
template.

In `@apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx`:
- Around line 71-79: The checkbox input in IntegrationSettings (the input using
checked={!!settings[key]} and onChange calling onSettingsChange) is not
programmatically linked to its help text (description), so add an accessible
relationship by giving the help text element a unique id (e.g. `${key}-help`)
and adding aria-describedby referring to that id on the input; ensure the id is
stable/unique per key and keep the help text element (the div with
className="help-text") targeted by aria-describedby so screen readers announce
the description when the checkbox receives focus.

In `@apps/vscode/src/providers/views/PageWelcome/styles.css`:
- Around line 162-165: The hover-only rule for left-panel links (.welcome-links
a:hover) lacks a keyboard-visible focus style, so add matching focus styles for
keyboard users by extending the selector to include :focus and preferably
:focus-visible (e.g., .welcome-links a:focus, .welcome-links a:focus-visible)
and apply the same color and underline (or an accessible alternative) to ensure
keyboard users receive an equivalent visual cue; update the existing
.welcome-links a:hover declaration to include these focus selectors and ensure
it meets contrast and visibility requirements for the left-panel links.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 57ee1e93-8fef-42e1-ab62-537162d91268

📥 Commits

Reviewing files that changed from the base of the PR and between 1041310 and 4255b0d.

📒 Files selected for processing (20)
  • apps/vscode/package.json
  • apps/vscode/src/config.ts
  • apps/vscode/src/connection/engine-installer.ts
  • apps/vscode/src/connection/engine-manager.ts
  • apps/vscode/src/deploy/docker-manager.ts
  • apps/vscode/src/deploy/service-linux.ts
  • apps/vscode/src/deploy/service-mac.ts
  • apps/vscode/src/deploy/service-manager.ts
  • apps/vscode/src/deploy/service-windows.ts
  • apps/vscode/src/providers/PageDeployProvider.ts
  • apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx
  • apps/vscode/src/providers/views/PageDeploy/styles.css
  • apps/vscode/src/providers/views/PageSettings/ConnectionSettings.tsx
  • apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx
  • apps/vscode/src/providers/views/PageSettings/PageSettings.tsx
  • apps/vscode/src/providers/views/PageWelcome/styles.css
  • apps/vscode/src/stubs/ssh2.js
  • packages/ai/src/ai/modules/task/task_engine.py
  • packages/ai/src/ai/node.py
  • packages/shared-ui/src/theme.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/shared-ui/src/theme.ts
  • apps/vscode/src/providers/views/PageSettings/PageSettings.tsx
  • apps/vscode/src/deploy/service-manager.ts

Comment thread apps/vscode/src/deploy/service-linux.ts
Comment thread apps/vscode/src/deploy/service-windows.ts
Comment thread apps/vscode/src/deploy/service-windows.ts Outdated
Comment thread apps/vscode/src/deploy/service-windows.ts
Comment thread apps/vscode/src/deploy/service-windows.ts Outdated
Comment thread apps/vscode/src/providers/PageDeployProvider.ts Outdated
Comment thread apps/vscode/src/providers/PageDeployProvider.ts
Comment thread apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx Outdated
stepmikhaylov
stepmikhaylov previously approved these changes Mar 18, 2026
@stepmikhaylov
stepmikhaylov enabled auto-merge (squash) March 18, 2026 15:25
@github-actions github-actions Bot added the docs Documentation label Mar 18, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/vscode/src/connection/engine-installer.ts (1)

843-855: ⚠️ Potential issue | 🟠 Major

httpStream() needs a timeout to prevent indefinite hangs.

If the HTTP request stalls during DNS lookup, TLS handshake, or socket connection, the promise remains pending forever, blocking downloadAsset() and preventing retries from triggering. The cancellation token only provides external timeout control; add an explicit timeout to the protocol.get() call (e.g., req.setTimeout()) so that stalled requests fail fast and allow the retry loop to proceed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/connection/engine-installer.ts` around lines 843 - 855, The
httpStream() promise can hang on stalled network phases; add an explicit socket
timeout on the outgoing request (use req.setTimeout(timeoutMs, ...) or
equivalent) inside httpStream so stalled requests are aborted and the promise is
rejected; on timeout call req.destroy() (or req.abort()) and reject with a clear
Error so downloadAsset()'s retry logic can proceed, and ensure you remove or
handle the timeout/error listeners to avoid leaks; keep the timeout value
configurable or use a reasonable default and apply the same behavior when
following redirects in httpStream().
🧹 Nitpick comments (3)
apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx (2)

32-55: Narrow integration key typing to integration-prefixed settings only.

BooleanKeys<SettingsData> also includes unrelated booleans (hasApiKey, autoConnect, etc.). Constraining keys to integration* avoids accidental misconfiguration later.

♻️ Proposed change
-type BooleanKeys<T> = { [K in keyof T]: T[K] extends boolean ? K : never }[keyof T];
+type IntegrationKey = Extract<keyof SettingsData, `integration${string}`>;

-const INTEGRATIONS: { key: BooleanKeys<SettingsData>; label: string; description: string }[] = [
+const INTEGRATIONS: { key: IntegrationKey; label: string; description: string }[] = [
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx` around
lines 32 - 55, The INTEGRATIONS array currently types keys with
BooleanKeys<SettingsData>, which can include non-integration booleans; create a
narrower template-lit type (e.g., IntegrationKeys<SettingsData>) that only
yields keys that both extend boolean and match the `integration${string}`
pattern, and replace BooleanKeys<SettingsData> with this new IntegrationKeys
type in the INTEGRATIONS declaration so INTEGRATIONS.key can only be an
integration-prefixed setting (referencing BooleanKeys and INTEGRATIONS to locate
where to change).

25-25: Use a type-only import for SettingsData to avoid circular dependency issues.

SettingsData is used only as a type in this file (for the interface and type constraints), creating an unnecessary runtime dependency. A circular import exists between this file and PageSettings.tsx, which import type will safely resolve.

♻️ Proposed change
-import { SettingsData } from './PageSettings';
+import type { SettingsData } from './PageSettings';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx` at line
25, Change the runtime import of SettingsData to a type-only import to break the
circular dependency: replace the current import of SettingsData with a type
import (i.e., use "import type { SettingsData } ...") wherever
IntegrationSettings.tsx references the SettingsData type (including the
interface and any type constraints) so the module is erased at compile time and
no runtime dependency on PageSettings remains.
apps/vscode/src/providers/views/PageWelcome/styles.css (1)

156-165: Keep the bottom links clearly link-like at rest.

After the switch to --left-fg, these anchors read very close to the surrounding copy until hover/focus. I’d keep a stronger default treatment here so they stay discoverable without interaction.

♻️ Possible tweak
 .welcome-links a {
-	color: var(--left-fg);
-	text-decoration: none;
+	color: var(--left-fg-strong);
+	text-decoration: underline;
+	text-underline-offset: 2px;
 	font-size: 12px;
 }
 
 .welcome-links a:hover,
 .welcome-links a:focus,
 .welcome-links a:focus-visible {
 	color: `#ffffff`;
-	text-decoration: underline;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode/src/providers/views/PageWelcome/styles.css` around lines 156 -
165, The .welcome-links a selector currently uses the muted variable (--left-fg)
making links hard to distinguish; update the default styling for .welcome-links
a to a stronger, more link-like appearance (e.g., a higher-contrast color and/or
underline) while keeping the existing hover/focus rules (.welcome-links a:hover,
.welcome-links a:focus, .welcome-links a:focus-visible) intact; modify the
.welcome-links a rule to use a clearly distinguishable color (or add
text-decoration: underline) so links are discoverable at rest without changing
the hover behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/vscode/src/config.ts`:
- Around line 499-501: getApiHost() now returns different values based on
connectionMode, but the .env sync logic only watches hostUrl so ROCKETRIDE_URI
can become stale; update the sync trigger to detect changes to the effective API
host (call getApiHost()) or to listen for connectionMode changes and invoke the
same env-sync routine used when hostUrl changes. Specifically, modify the code
that currently compares hostUrl (the existing .env sync path) to instead compute
currentApiHost = getApiHost() (or add a ConnectionManager.on('modeChange', ...)
handler) and call the existing env sync function so ROCKETRIDE_URI is updated
whenever getApiHost() or connectionMode changes.
- Around line 478-485: The current trace detection uses
argsStr.includes('--trace=') (hasTrace) and misses the space-separated form like
"--trace servicePython", causing an extra flag to be appended; update the
detection logic used by hasTrace to check for either "--trace=" or "--trace "
(for example via a regex such as /--trace(?:=|\s+)/) so that when inspecting
argsStr before pushing '--trace=servicePython' (in the block referencing
config.local.debugOutput and result.push('--trace=servicePython')), the code
correctly recognizes both forms and avoids adding a duplicate trace flag.

In `@apps/vscode/src/connection/engine-installer.ts`:
- Around line 157-161: ensureLockFileExists currently writes the lock file
without ensuring the parent directory exists, causing ENOENT when called from
uninstall() or cleanupOldVersions(); update ensureLockFileExists (used alongside
lockFilePath()) to first ensure this.enginesRoot (or the directory returned by
lockFilePath()) exists by creating the directory if missing (e.g., mkdirSync
with { recursive: true }) before calling writeFileSync, so the lock file
creation becomes a no-op on fresh profiles and mirrors install() behavior.

In `@apps/vscode/src/deploy/service-linux.ts`:
- Around line 86-95: The remove() method currently swallows all errors from
runSudo calls and always prints success; change it to only ignore "not
installed"/ENOENT cases and otherwise propagate or surface failures so the
success message is not shown on partial/total failure. Specifically, in remove()
(references: runSudo, UNIT_NAME, UNIT_PATH, INSTALL_ROOT, logger.output,
icons.success) wrap each runSudo call to check the thrown error: if it indicates
"not found"/"Unit ... not found" or filesystem ENOENT for rm, treat as non-fatal
and continue, but for any other error record it (or rethrow) and abort printing
the success message; alternatively aggregate non-ignored errors and if the
aggregate is non-empty log the error(s) and throw/return before calling
logger.output(`${icons.success} Service removed`). Ensure systemctl
daemon-reload still runs on success path and that only the legitimate "already
absent" conditions are suppressed.
- Around line 55-62: prepareInstallRoot currently chowns INSTALL_ROOT to the
interactive user which enables privilege escalation; change ownership and
permissions so the service does not run as root and cannot execute user-modified
payloads: in prepareInstallRoot replace the runSudo('chown', ['-R', username,
INSTALL_ROOT]) with ownership root:root and restrictive perms (e.g.,
runSudo('chown', ['-R', 'root:root', INSTALL_ROOT]) and runSudo('chmod', ['-R',
'0755', INSTALL_ROOT]) or make only specific runtime subdirs writable). In
parallel, update buildUnitFile() to set a dedicated unprivileged service account
(add User=<serviceUser> and Group=<serviceUser> in the generated unit; create or
ensure that <serviceUser> exists during install) so the unit never executes as
root; reference functions buildUnitFile(), prepareInstallRoot(), INSTALL_ROOT
and LinuxServiceManager.ENGINE_DEPS when making these edits.
- Around line 164-189: The runSudo function must handle spawn() errors and avoid
unguarded stdio dereferences: immediately attach a child.on('error', ...)
handler after calling spawn('sudo', ...) to reject the Promise with the emitted
error, and change accesses to child.stdin/child.stdout/child.stderr to use
optional chaining (e.g., child.stdin?.write, child.stdin?.end, child.stdout?.on,
child.stderr?.on) so a missing stdio won't throw; keep the existing close
handler to resolve/reject on exit but ensure the error handler short-circuits
and rejects the same Promise when spawn fails (refer to runSudo, spawn,
this.sudoPassword, and the child.on('close', ...) logic).

In `@apps/vscode/src/deploy/service-mac.ts`:
- Around line 79-87: The remove() method currently swallows all errors from the
privileged commands so it always logs success; change it to surface failures
instead: for each runSudo call (unload/remove PLIST_PATH and rm -rf
INSTALL_ROOT/CONFIG_DIR) catch and collect errors (or rethrow) instead of
ignoring, log a descriptive failure via this.logger.error including the command
and error, and return/throw a non-success result so the caller/ UI can show
cleanup failed; keep references to remove(), runSudo(), PLIST_PATH,
INSTALL_ROOT, CONFIG_DIR and the existing this.logger.output/this.logger.error
to locate and update the code paths.
- Around line 53-60: prepareInstallRoot currently makes INSTALL_ROOT (e.g.,
/Library/RocketRide) user-owned via runSudo('chown', ['-R', ...]) which is
dangerous because the LaunchDaemon plist lacks a UserName key and will run as
root; either stop changing ownership (keep INSTALL_ROOT root-owned) or ensure
the daemon will run as the interactive user by adding UserName to the
LaunchDaemon plist. To fix: remove or narrow the chown call in
prepareInstallRoot (refer to prepareInstallRoot, runSudo, chown, INSTALL_ROOT,
CONFIG_DIR) so system-owned files remain root-owned, or alternatively add a
UserName entry to the LaunchDaemon plist so the service runs as the interactive
user; pick one approach and update the corresponding code/path and plist
accordingly.
- Around line 184-207: The runSudo function currently spawns a child process
with spawn('sudo', ...) but doesn't listen for the child's 'error' event and
assumes stdio streams exist; add a child.on('error', ...) handler that rejects
the Promise with the spawn error to avoid hanging when spawn fails (e.g., sudo
not found), and guard access to child.stdin, child.stdout and child.stderr
(check for existence before calling write(), end(), or attaching 'data'
listeners) so the promise always resolves or rejects cleanly even if streams are
null; reference runSudo, spawn, child, this.sudoPassword, child.stdin,
child.stdout, child.stderr and ensure the existing child.on('close', ...) logic
still resolves/rejects as before.

In `@apps/vscode/src/deploy/service-windows.ts`:
- Around line 63-85: The install() method currently relies on NSSM defaults
(LocalSystem); update the NSSM configuration sequence passed to
runElevatedScript to explicitly set the service account via an ObjectName entry
(using psCmd(NSSM_PATH, 'set', SERVICE_NAME, 'ObjectName', '<DOMAIN\\User>' or
'.\\<username>') placed alongside the other psCmd(...) calls before starting the
service), and either create/configure a dedicated low-privilege account
beforehand or add a comment documenting why LocalSystem is required if you
decide not to change it; reference install(), runElevatedScript, psCmd,
NSSM_PATH, and SERVICE_NAME when making the change.
- Around line 266-292: The downloadFile method currently has no request/response
timeouts; add a bounded timeout (e.g., configurable constant) that aborts the
HTTP request and destroys the response/stream if the download stalls, cleans up
the file write stream, clears timers and listeners, and rejects with a clear
timeout error; implement this by starting a timer when the request is made (and
reset/refresh it on response or socket 'data' events), calling req.abort() or
req.destroy() and response.destroy() on timeout, ensuring file.close() and
unlink(destPath) as needed on error, and removing the timer on success/failure;
apply the same pattern to httpStream in engine-installer.ts so both downloadFile
and httpStream use request + response timeouts and proper cleanup.

In `@apps/vscode/src/providers/PageDeployProvider.ts`:
- Around line 267-287: The short-circuit currently only compares
currentConfig.version to newVersion which misses tag moves; change the check in
PageDeployProvider so it also verifies the installed publishedAt matches the
config before returning early: call installer.getInstalledPublishedAt(channel)
and require currentConfig.publishedAt === that value (in addition to the
version/tag check, or replace with comparing versionSpec+publishedAt) so the
branch only short-circuits when both version identity and publishedAt match;
keep existing calls to installer.getInstalledVersion and writeServiceConfig
as-is but use the publishedAt comparison when deciding "Already up to date".

In `@packages/ai/src/ai/modules/task/task_engine.py`:
- Around line 1466-1471: The current check only looks for tokens starting with
'--trace=' and can miss a separate '--trace' token (and its value) in
child_args; update the detection to treat either a token that starts with
'--trace=' or a standalone '--trace' followed by a value as “already present”
(e.g., check any(a.startswith('--trace=') or a == '--trace' for a in child_args)
and also consider the case where '--trace' appears at index i and
child_args[i+1] is its value). In the startup_args() inheritance loop, when
picking up the parent's trace option, handle both forms: if you find '--trace='
append as before; if you find a standalone '--trace' in startup_args, append
both the '--trace' token and its value (the next token) to child_args, and
ensure you don't append duplicates when child_args already contains either form.

---

Outside diff comments:
In `@apps/vscode/src/connection/engine-installer.ts`:
- Around line 843-855: The httpStream() promise can hang on stalled network
phases; add an explicit socket timeout on the outgoing request (use
req.setTimeout(timeoutMs, ...) or equivalent) inside httpStream so stalled
requests are aborted and the promise is rejected; on timeout call req.destroy()
(or req.abort()) and reject with a clear Error so downloadAsset()'s retry logic
can proceed, and ensure you remove or handle the timeout/error listeners to
avoid leaks; keep the timeout value configurable or use a reasonable default and
apply the same behavior when following redirects in httpStream().

---

Nitpick comments:
In `@apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx`:
- Around line 32-55: The INTEGRATIONS array currently types keys with
BooleanKeys<SettingsData>, which can include non-integration booleans; create a
narrower template-lit type (e.g., IntegrationKeys<SettingsData>) that only
yields keys that both extend boolean and match the `integration${string}`
pattern, and replace BooleanKeys<SettingsData> with this new IntegrationKeys
type in the INTEGRATIONS declaration so INTEGRATIONS.key can only be an
integration-prefixed setting (referencing BooleanKeys and INTEGRATIONS to locate
where to change).
- Line 25: Change the runtime import of SettingsData to a type-only import to
break the circular dependency: replace the current import of SettingsData with a
type import (i.e., use "import type { SettingsData } ...") wherever
IntegrationSettings.tsx references the SettingsData type (including the
interface and any type constraints) so the module is erased at compile time and
no runtime dependency on PageSettings remains.

In `@apps/vscode/src/providers/views/PageWelcome/styles.css`:
- Around line 156-165: The .welcome-links a selector currently uses the muted
variable (--left-fg) making links hard to distinguish; update the default
styling for .welcome-links a to a stronger, more link-like appearance (e.g., a
higher-contrast color and/or underline) while keeping the existing hover/focus
rules (.welcome-links a:hover, .welcome-links a:focus, .welcome-links
a:focus-visible) intact; modify the .welcome-links a rule to use a clearly
distinguishable color (or add text-decoration: underline) so links are
discoverable at rest without changing the hover behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1863c9e6-09a8-450a-8c89-1e6351b227bb

📥 Commits

Reviewing files that changed from the base of the PR and between 4255b0d and 419a37e.

📒 Files selected for processing (12)
  • apps/vscode/src/config.ts
  • apps/vscode/src/connection/engine-installer.ts
  • apps/vscode/src/deploy/service-linux.ts
  • apps/vscode/src/deploy/service-mac.ts
  • apps/vscode/src/deploy/service-windows.ts
  • apps/vscode/src/providers/PageDeployProvider.ts
  • apps/vscode/src/providers/views/PageDeploy/PageDeploy.tsx
  • apps/vscode/src/providers/views/PageDeploy/styles.css
  • apps/vscode/src/providers/views/PageSettings/IntegrationSettings.tsx
  • apps/vscode/src/providers/views/PageWelcome/styles.css
  • docs/README-vscode.md
  • packages/ai/src/ai/modules/task/task_engine.py
✅ Files skipped from review due to trivial changes (1)
  • docs/README-vscode.md

Comment thread apps/vscode/src/config.ts
Comment thread apps/vscode/src/config.ts
Comment thread apps/vscode/src/connection/engine-installer.ts
Comment thread apps/vscode/src/deploy/service-linux.ts
Comment thread apps/vscode/src/deploy/service-linux.ts
Comment thread apps/vscode/src/deploy/service-mac.ts
Comment thread apps/vscode/src/deploy/service-windows.ts
Comment thread apps/vscode/src/deploy/service-windows.ts
Comment thread apps/vscode/src/providers/PageDeployProvider.ts
Comment thread packages/ai/src/ai/modules/task/task_engine.py
@Rod-Christensen
Rod-Christensen marked this pull request as draft March 18, 2026 19:23
auto-merge was automatically disabled March 18, 2026 19:23

Pull request was converted to draft

@Rod-Christensen
Rod-Christensen marked this pull request as ready for review March 18, 2026 19:24
@Rod-Christensen
Rod-Christensen merged commit 9ea9c62 into develop Mar 18, 2026
33 of 40 checks passed
@Rod-Christensen
Rod-Christensen deleted the feat/vscode-enhancements branch March 18, 2026 19:48
asclearuc added a commit that referenced this pull request Aug 8, 2026
With on_launch and on_terminate relocated, nothing in cmd_debug.py is
reachable: the extension's debugger has been disabled since #268 and no live
client sends initialize, attach, pause, continue, configurationDone, threads
or disconnect. Delete the file, unregister DebugCommands from TaskConn, and
drop TaskConn.request() / on_command() — the pair that forwarded unhandled
DAP commands to debugpy.

on_disconnect is deleted rather than relocated: stripped of its debugger
parts it returns build_response(request), which is byte-for-byte what
dap_conn's unhandled-command fallback already produces.

Removing request()/on_command() also drops their rrext_ guard. No privilege
change — all 36 rrext_ commands dispatch by name via on_{command} and never
reached it; only unknown or misspelled ones did, and those now get the same
empty-success fallback as any other unknown command. A new test pins the 36
handlers in place.

Refs #1844
asclearuc added a commit that referenced this pull request Aug 11, 2026
With on_launch and on_terminate relocated, nothing in cmd_debug.py is
reachable: the extension's debugger has been disabled since #268 and no live
client sends initialize, attach, pause, continue, configurationDone, threads
or disconnect. Delete the file, unregister DebugCommands from TaskConn, and
drop TaskConn.request() / on_command() — the pair that forwarded unhandled
DAP commands to debugpy.

on_disconnect is deleted rather than relocated: stripped of its debugger
parts it returns build_response(request), which is byte-for-byte what
dap_conn's unhandled-command fallback already produces.

Removing request()/on_command() also drops their rrext_ guard. No privilege
change — all 36 rrext_ commands dispatch by name via on_{command} and never
reached it; only unknown or misspelled ones did, and those now get the same
empty-success fallback as any other unknown command. A new test pins the 36
handlers in place.

Refs #1844
asclearuc added a commit that referenced this pull request Sep 11, 2026
With on_launch and on_terminate relocated, nothing in cmd_debug.py is
reachable: the extension's debugger has been disabled since #268 and no live
client sends initialize, attach, pause, continue, configurationDone, threads
or disconnect. Delete the file, unregister DebugCommands from TaskConn, and
drop TaskConn.request() / on_command() — the pair that forwarded unhandled
DAP commands to debugpy.

on_disconnect is deleted rather than relocated: stripped of its debugger
parts it returns build_response(request), which is byte-for-byte what
dap_conn's unhandled-command fallback already produces.

Removing request()/on_command() also drops their rrext_ guard. No privilege
change — all 36 rrext_ commands dispatch by name via on_{command} and never
reached it; only unknown or misspelled ones did, and those now get the same
empty-success fallback as any other unknown command. A new test pins the 36
handlers in place.

Refs #1844
asclearuc added a commit that referenced this pull request Sep 14, 2026
With on_launch and on_terminate relocated, nothing in cmd_debug.py is
reachable: the extension's debugger has been disabled since #268 and no live
client sends initialize, attach, pause, continue, configurationDone, threads
or disconnect. Delete the file, unregister DebugCommands from TaskConn, and
drop TaskConn.request() / on_command() — the pair that forwarded unhandled
DAP commands to debugpy.

on_disconnect is deleted rather than relocated: stripped of its debugger
parts it returns build_response(request), which is byte-for-byte what
dap_conn's unhandled-command fallback already produces.

Removing request()/on_command() also drops their rrext_ guard. No privilege
change — all 36 rrext_ commands dispatch by name via on_{command} and never
reached it; only unknown or misspelled ones did, and those now get the same
empty-success fallback as any other unknown command. A new test pins the 36
handlers in place.

Refs #1844
asclearuc added a commit that referenced this pull request Sep 14, 2026
* refactor(ai): relocate on_launch and on_terminate to TaskCommands

on_launch is the cloud pipeline-launch path (the SaaS ALB's PUT /task
dispatches here) and on_terminate is the SDK's pipeline stop. Neither is
debugging; both sat on DebugCommands for historical reasons. Move them ahead
of the debugger removal so deleting cmd_debug.py cannot take live production
paths with it.

Relocation only — task.debug and attach_debugger=True are left as they are,
each changed in its own follow-up rather than riding along inside a code move.

The debugger-session bookkeeping cannot follow the methods, since TaskCommands
holds no _debug_* state: on_launch drops its "already active" guard, stops
recording _debug_id/_debug_token, and returns None instead of an 'initialized'
event (the real reply already went out via send_response, and its only
consumer suppressed it). on_terminate no longer falls back to _debug_token —
both live callers pass the token explicitly. The stray-teamId error now
matches on_execute's wording.

Tests: 5 cases move to test_cmd_task.py rewritten against TaskCommands, 1 is
dropped with the guard it covered, 1 is added for the new return contract —
no net change to the packages/ai count.

Refs #1844

* refactor(ai): delete cmd_debug.py and the debugpy forwarding path

With on_launch and on_terminate relocated, nothing in cmd_debug.py is
reachable: the extension's debugger has been disabled since #268 and no live
client sends initialize, attach, pause, continue, configurationDone, threads
or disconnect. Delete the file, unregister DebugCommands from TaskConn, and
drop TaskConn.request() / on_command() — the pair that forwarded unhandled
DAP commands to debugpy.

on_disconnect is deleted rather than relocated: stripped of its debugger
parts it returns build_response(request), which is byte-for-byte what
dap_conn's unhandled-command fallback already produces.

Removing request()/on_command() also drops their rrext_ guard. No privilege
change — all 36 rrext_ commands dispatch by name via on_{command} and never
reached it; only unknown or misspelled ones did, and those now get the same
empty-success fallback as any other unknown command. A new test pins the 36
handlers in place.

Refs #1844

* refactor(ai): remove debugpy port passing, node bootstrap and the DAP-over-TCP client

Completes the pipeline-debugger removal: the command handlers went in the
previous commit, this drops the machinery they drove. task_engine.py no
longer assigns a debug port or passes --debug_port / --debug_host /
--wait_for_client to the subprocess; node.py loses the argparse block and
the debugpy.listen() / wait_for_client() bootstrap that consumed them;
dbg_debugpy.py and transport_tcpip.py are deleted, having existed solely to
reach the listener node.py no longer starts; and requirements.txt goes with
its depends() call, being the repo's only debugpy declaration.

Deleting DbgDebugpy forces the rest: TaskDbgDebugpy, _debug_python and its
cleanup, Task.attach_task() which existed only to construct it, and in turn
TaskServer.attach_task(), the attach gate and the unused attach_debugger
parameter. _debug_port, is_debug_available() and _noDebug lose their last
writer or reader along with the port passing.

Deliberately untouched: the engine->python shim and the C++ setupDebug()
thread registration, which serve debugging our own Python under the IDE
rather than the pipeline debugger; also task.debug, debuggerAttached in the
SDKs, the extension files and the docs.

Tests: test_transport_tcpip.py deleted with the transport (23 cases), plus
the two tests whose subjects no longer exist. packages/ai 1870 -> 1845.

Refs #1844

* fix(ai): bind token before the try in on_terminate

The failure log in the except block references `token`, but its assignment
lives inside the try — so when get_task_token() itself raises, the handler
dies with UnboundLocalError and the real error never surfaces.

Pre-existing, carried over verbatim by the relocation in 39a6d85 and
caught by review on #1886.

Refs #1844

* test(ai): cover the authorization refusal in on_terminate

on_terminate gates stopping a task behind get_task's task.control check,
but only the allowed path was covered. Adds the negative case: when the
lookup raises PermissionError the handler must propagate it and stop_task
must never be awaited.

Raised by review on #1886.

Refs #1844

* test(ai): assert on_terminate checks task.control specifically

The mock raised on any get_task call, so the refusal test would have
passed even if on_terminate requested the wrong permission — or none.
Assert the exact call instead.

Raised by review on #1886.

Refs #1844

* fix(ai): refuse unhandled DAP commands instead of returning success

Removing on_command left TaskConn falling through to DAPConn's default,
which builds a SUCCESS response for a command nobody handled. That is a
behaviour change the removal did not intend: a misspelled rrext_* in a
script, or a stale client still sending the debugger commands, would read
success: true and conclude the command worked.

Restores a minimal on_command that only refuses. The old error strings are
deliberately not restored — 'Invalid command' applied solely to rrext_*,
while everything else surfaced 'Task token is required' from the debugpy
path, which was an artifact rather than an answer. Nothing in either repo
matches on those strings.

The test now pins the property rather than the absence of the method.
Also corrects three stale lines in eaas.py's docstring: attach and
disconnect went with cmd_debug.py, and the proxying claim described the
forwarding that was removed.

Raised by review on #1886.

Refs #1844

* fix(ai): adopt the devTeam rename in the relocated on_launch

While this branch was out, #1993 renamed AccountInfo.defaultTeam to
devTeam. The relocated on_launch kept the old name, and git merged
cmd_task.py without a conflict, so nothing flagged it — every launch
would have raised AttributeError.

That same commit added a guard beside the rename, in on_execute and in
cmd_debug.on_launch, the very function this branch relocates. Carrying
it over is not a new rule: leaving it out would have deleted a guard
develop has today and let an empty team reach verify_team_permission
and the org resolution. The wording is the on_execute variant, since
after this branch the path is no longer the debugger.

Test maintenance forced by the same drift. Two call sites still passed
the helper's old snake_case default_team. And
test_rrext_handlers_still_dispatch_by_name pinned an exact handler
count that upstream invalidated by consolidating six app handlers into
on_rrext_app (37 -> 35); the branch's handler set is byte-identical to
develop's, so the count becomes a floor rather than a census.

The new test pins that an empty devTeam refuses before the permission
check and before start_task — coverage develop has on neither copy of
the guard.

Gate: 2894 passed, 122 skipped.

Refs #1844
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation module:ai AI/ML modules module:ui Chat UI and Dropper UI module:vscode VS Code extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants