Skip to content

feat(desktop): add managed remote Runtime Host onboarding - #3236

Open
M4n5ter wants to merge 17 commits into
mainfrom
feat/desktop-managed-remote-onboarding
Open

feat(desktop): add managed remote Runtime Host onboarding#3236
M4n5ter wants to merge 17 commits into
mainfrom
feat/desktop-managed-remote-onboarding

Conversation

@M4n5ter

@M4n5ter M4n5ter commented Aug 19, 2026

Copy link
Copy Markdown
Member

Summary

English
  • Add an Add computer flow that installs, pairs, verifies, and connects a Linux Runtime Host from one SSH destination
  • Keep credentials in the main process, preserve manual profiles for advanced endpoints, and continue directly to remote Project selection
  • Let unpackaged Desktop builds upload a content-addressed local CLI archive for realistic development testing
  • Require packaged Desktop releases to select an exact Maka CLI setup package that is already published

Fixes #3233

简体中文
  • 新增添加电脑流程,只需一个 SSH destination,即可完成 Linux Runtime Host 的安装、配对、验证与连接
  • Credential 始终留在 main process;高级 endpoint 仍可手工配置;连接成功后可直接选择远端 Project
  • 未打包 Desktop 可上传带内容版本的本地 CLI archive,用于真实开发验证
  • 打包 Desktop 时必须显式选择一个已经发布的精确 Maka CLI setup package

修复 #3233

Verification

English
  • npm run build:test
  • npm run typecheck
  • npm run lint
  • npm run format:check
  • 53 focused Desktop and Runtime Host tests passed
  • Computer Use against a real Linux systemd user service: uploaded a development archive, connected the Host, and loaded the remote Project picker
简体中文
  • npm run build:test
  • npm run typecheck
  • npm run lint
  • npm run format:check
  • 53 项 Desktop 与 Runtime Host 定向测试通过
  • 使用 Computer Use 对真实 Linux systemd user service 验证:上传开发 archive、连接 Host,并成功加载远端 Project picker

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: OpenAI Codex implemented the Desktop onboarding flow, SSH orchestration, development-package path, tests, and documentation under maintainer direction

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2fabf7eb-c533-445e-8630-1dbc6b5858b7

📥 Commits

Reviewing files that changed from the base of the PR and between 0673602 and 26ddf61.

📒 Files selected for processing (15)
  • apps/desktop/electron-builder.config.mjs
  • apps/desktop/src/main/runtime-host-boot.ts
  • apps/desktop/src/main/runtime-host-client.ts
  • apps/desktop/src/main/runtime-host-pairing-journal.ts
  • apps/desktop/src/preload/bridge-contract.d.ts
  • apps/desktop/src/preload/preload.ts
  • apps/desktop/src/renderer/settings/settings-surface.tsx
  • apps/desktop/stories/settings/settings-pages.stories.tsx
  • packages/cli/src/__tests__/runtime-host-cli-context.test.ts
  • packages/runtime-host/src/client/host-profile.ts
  • packages/runtime-host/src/client/index.ts
  • packages/runtime-host/src/protocol/index.ts
  • packages/runtime-host/src/server/connection-session.ts
  • packages/runtime-host/src/server/host-kernel.ts
  • scripts/release-cli-package.mjs
📝 Walkthrough

Problem solved

This PR adds a guided Desktop flow for adding a Linux Runtime Host through SSH. The flow:

  • Accepts an SSH destination, optional port, and optional display name.
  • Installs, pairs, verifies, and connects the Runtime Host.
  • Shows setup progress and supports cancellation, retry, reset, and actionable errors.
  • Keeps credentials in the main process and credential store.
  • Redacts credentials from renderer state and terminal output.
  • Saves the Profile only after verification.
  • Opens remote Project selection after success.
  • Preserves manual TLS and SSH Profile configuration.

Source of truth

The PR extends the existing Desktop, CLI, Runtime Host, Profile, credential, OpenSSH, systemd, and IPC contracts. It does not add a separate credential authority or Profile system.

The guided flow adds a new onboarding entry point over the existing managed setup path. Manual Profile configuration remains a separate supported path.

Solution size and complexity

The implementation is the smallest coherent solution for the requested transactional behavior.

The prepare/finalize credential flow prevents credential commitment before verification. Retry handling addresses connection loss and unknown commit outcomes. Profile rebinding and rollback protect existing Profiles. SSH cancellation escalation prevents setup processes and archive uploads from remaining active.

The implementation adds coordination across the main process, preload bridge, renderer, CLI, and Runtime Host protocol. This scope matches the requested onboarding, security, recovery, and development-package behavior.

The following code may be simplified later without weakening behavior if equivalent coverage remains:

  • Shared setup-frame parsing and process-lifecycle helpers.
  • Repeated onboarding snapshot and callback plumbing.
  • Repeated test fixtures for credential and Profile failure cases.

Rollback, retry, cancellation, redaction, and failure-path tests should remain. They cover security-sensitive and transactional behavior.

Validation and risks

The PR reports:

  • 943 passing Desktop tests.
  • Successful typecheck, lint, and format checks.
  • Verification against a real Linux systemd user service.

The tests cover credential and progress isolation, pairing finalization recovery, cancellation and forced termination, Profile replacement and rollback, credential preparation and revocation, development archive upload, service restart behavior, compatibility handling, and release-version handling.

These checks were not independently verified in the available evidence. Required-check status remains unverified.

Complexity delta

The PR adds:

  • Managed onboarding orchestration in the Desktop main process.
  • Revisioned onboarding snapshots and lifecycle states.
  • IPC methods for snapshot retrieval, start, cancel, reset, and subscription.
  • SSH setup, framed progress parsing, archive upload, timeouts, and termination escalation.
  • Prepare/finalize credential states with expiry and unknown-commit handling.
  • Profile rebinding and transactional rollback.
  • A compatibility epoch increment.
  • Packaged and development setup-package resolution.
  • Renderer callbacks, localized setup states, and post-onboarding Project selection.
  • Additional failure and recovery test maintenance.

The PR removes or consolidates:

  • Repeated credential mutation logic through shared issue, prepare, replace, and revoke flows.
  • Ambiguous development package versions through deterministic content-addressed versions.
  • Direct replacement cleanup paths where conditional rebinding is required.
  • Restart=on-failure, replaced by Restart=always.

Total maintenance complexity increases locally. The increase is justified by the required credential isolation, transactional pairing, retry safety, cancellation guarantees, and packaged/development setup support. The reported validation supports this conclusion, but required checks remain unverified.

Review-relevant risks

  • The PR changes user-visible Desktop onboarding, navigation, localized text, and remote Project selection. Material changes in these areas require independent human review under repository policy.
  • The PR changes public preload types, Desktop APIs, CLI setup options, Runtime Host protocol operations, and the compatibility epoch. Material public-contract changes require independent human review under repository policy.
  • The PR changes credential issuance, preparation, finalization, expiry, revocation, storage, and rollback. Material security changes require independent human review under repository policy.
  • The PR changes CLI versions, release manifests, packaged setup metadata, development archives, and systemd restart behavior. Material release and operational changes require independent human review under repository policy.
  • The PR adds contribution guidance and remote setup documentation. Material governance or operational-process changes require independent human review under repository policy.

The person performing the merge reviews the final diff. A maintainer makes the final determination.

Walkthrough

Desktop adds guided SSH onboarding for remote Runtime Hosts. The change adds deferred credential pairing, durable pairing recovery, framed SSH setup, onboarding IPC, Settings UI, project selection, CLI package handling, and validation coverage.

Changes

Remote Runtime Host onboarding

Layer / File(s) Summary
Credential pairing and profile recovery
packages/runtime-host/src/protocol/*, packages/runtime-host/src/server/*, packages/runtime-host/src/client/host-profile.ts, apps/desktop/src/main/runtime-host-profile-service.ts
Credentials support preparation, finalization, expiry, uncertain commit outcomes, and conditional profile rebinding. Desktop journals pairing intents and recovers or rolls back interrupted pairing.
CLI setup and package deployment
packages/cli/src/*, scripts/release-cli-package.mjs, packages/cli/package.json
CLI setup parses deferred pairing, prepares and revokes credentials, accepts compatible development versions, and configures service restart behavior.
Desktop SSH setup and onboarding orchestration
apps/desktop/src/main/runtime-host-*.ts, apps/desktop/electron-builder.config.mjs
Desktop resolves setup packages, uploads development archives, runs setup over SSH, parses progress frames, manages cancellation, validates endpoints, finalizes pairing, and persists verified profiles.
Onboarding bridge and Settings flow
apps/desktop/src/preload/*, apps/desktop/src/renderer/settings/*, apps/desktop/src/renderer/app-shell.tsx, apps/desktop/src/renderer/use-new-task-target.ts
IPC exposes onboarding snapshots. Settings provides Add computer, progress, retry, cancellation, completion, and project selection flows.
Validation and documentation
apps/desktop/src/main/__tests__/*, packages/cli/src/__tests__/*, packages/runtime-host/src/__tests__/*, CONTRIBUTING*, docs/*
Tests cover pairing recovery, cancellation, credential isolation, rollback, package replacement, service behavior, and local archive verification.

Estimated code review effort: 5 (Critical) | ~120 minutes

Merge Risk: 🟠 High · up to 06736

This PR adds managed Linux Runtime Host onboarding, pairing, and deployment, but the current implementation can expose pairing credentials in recovery files, resurrect revoked credentials after failed writes, ignore deployment cleanup failures, and leave onboarding stuck without an error message. These security, rollback, and usability risks should be fixed before merge.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Settings
  participant DesktopOnboarding
  participant SSH
  participant RuntimeHost
  User->>Settings: Enter SSH destination
  Settings->>DesktopOnboarding: Start onboarding
  DesktopOnboarding->>SSH: Run setup package
  SSH->>RuntimeHost: Upload and execute setup
  RuntimeHost-->>SSH: Return progress and credential
  SSH-->>DesktopOnboarding: Return setup result
  DesktopOnboarding->>RuntimeHost: Verify and finalize pairing
  DesktopOnboarding-->>Settings: Publish completed profile
  Settings->>User: Open project picker
Loading

Possibly related issues

  • maka-agent/maka-agent#3228 — Covers the tracked Desktop Add Computer SSH onboarding and pairing flow.

Possibly related PRs

Suggested reviewers: astro-han

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: managed remote Runtime Host onboarding for Desktop.
Description check ✅ Passed The description includes the required summary, issue reference, verification results, AI disclosure, checklist, behavior change, and supporting evidence.
Linked Issues check ✅ Passed The changes implement the linked issue’s Add Computer flow, SSH setup, credential protection, verification, recovery, retries, project selection, and manual profile compatibility.
Out of Scope Changes check ✅ Passed The changes support the onboarding objectives and include aligned tests, documentation, protocol updates, recovery logic, and development-package support without clear unrelated scope.
Ai Use Disclosure ✅ Passed The PR selects generative tooling and names OpenAI Codex with scope; all 10 introduced commits contain standalone Generated-by: Codex trailers consistent with that disclosure.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat/desktop-managed-remote-onboarding
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/desktop-managed-remote-onboarding

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

@M4n5ter
M4n5ter force-pushed the feat/desktop-managed-remote-onboarding branch from 956157c to 0427296 Compare August 19, 2026 05:47
@M4n5ter

M4n5ter commented Aug 19, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

@qodo-code-review

qodo-code-review Bot commented Aug 19, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Project continuation rejects unhandled ✓ Resolved 🐞 Bug ☼ Reliability
Description
Fix-now: the new onRemoteHostAdded callback discards chooseProjectForProfile() without a
rejection handler, while that function propagates catalog-refresh failures. A transient
newTasks.getCatalog() failure therefore produces an unhandled promise rejection instead of a
controlled post-onboarding fallback.
Code

apps/desktop/src/renderer/app-shell.tsx[3594]

+          void newTask.chooseProjectForProfile(profileId);
Relevance

●●● Strong

Recent renderer precedent accepts preventing unhandled asynchronous failures.

PR-#3147

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
chooseProjectForProfile() directly awaits refresh(), and refresh() rethrows after recording
its error state. Other fire-and-forget refresh callers explicitly attach .catch(() => undefined),
whereas the newly added call does not.

apps/desktop/src/renderer/app-shell.tsx[3591-3595]
apps/desktop/src/renderer/use-new-task-target.ts[39-60]
apps/desktop/src/renderer/use-new-task-target.ts[63-67]
apps/desktop/src/renderer/use-new-task-target.ts[146-160]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The post-onboarding project-picker continuation can reject when catalog refresh fails, and its only caller discards that rejection.

## Issue Context
Reuse the renderer's existing fire-and-forget refresh pattern by attaching a rejection handler, or contain the error inside `chooseProjectForProfile()` while preserving its existing error state/toast behavior. New UI state or public API is unnecessary.

## Fix Focus Areas
- apps/desktop/src/renderer/app-shell.tsx[3591-3595]
- apps/desktop/src/renderer/use-new-task-target.ts[146-160]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Shutdown delays SSH termination ✓ Resolved 🐞 Bug ☼ Reliability
Description
Fix-now: closeRuntimeHostDesktop() waits for onboarding to close before calling
runtimeHostSshTerminal.close(), but onboarding deliberately does not abort after setup emits its
completion frame and instead awaits the setup task. If that SSH process remains alive, application
shutdown cannot terminate it until the setup path's ten-minute timeout expires.
Code

apps/desktop/src/main/runtime-host-boot.ts[1366]

+    runtimeHostOnboarding.close().then(() => runtimeHostSshTerminal.close()),
Relevance

●●● Strong

Recent accepted teardown fixes prevent long-lived stuck operations.

PR-#3111
PR-#3048

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Onboarding marks the operation non-cancellable at commit, and its close() only aborts cancellable
work before awaiting the task. The task waits on SSH with a ten-minute timeout, while the changed
shutdown line invokes the SSH close only after that wait resolves.

apps/desktop/src/main/runtime-host-boot.ts[1363-1367]
apps/desktop/src/main/runtime-host-onboarding.ts[73-80]
apps/desktop/src/main/runtime-host-onboarding.ts[153-160]
apps/desktop/src/main/runtime-host-ssh-terminal.ts[317-321]
apps/desktop/src/main/runtime-host-ssh-terminal.ts[35-37]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Desktop shutdown serializes onboarding closure before SSH-terminal closure. During the non-cancellable commit phase, onboarding waits for the SSH process, while the code capable of terminating that process cannot run.

## Issue Context
Reuse both existing `close()` seams as independent entries in the existing `Promise.allSettled` shutdown sequence rather than chaining them. No new state, branch, configuration, or public surface is required.

## Fix Focus Areas
- apps/desktop/src/main/runtime-host-boot.ts[1363-1367]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Unknown finalize hangs forever ✗ Dismissed 🐞 Bug ☼ Reliability
Description
Fix-now: finalizePairing() classifies commit_outcome_unknown as retryable but then waits for a
replacement connection, even though that error can be returned over a still-healthy connection. With
no reconnect event, managed onboarding remains in connecting_host indefinitely and never completes
or fails.
Code

apps/desktop/src/main/runtime-host-desktop-manager.ts[R251-253]

+        const retry = pairingFinalizeRetry(error);
+        if (!retry) throw error;
+        candidate = await this.#waitForReadyCandidate(lifecycle, candidate);
Relevance

●●● Strong

Recent Runtime Host fixes require terminal outcomes rather than indefinite pending.

PR-#3048

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The server explicitly converts a post-publication persistence uncertainty into a
commit_outcome_unknown operation error without closing the connection. The manager then passes the
current candidate as previous, and waitForCurrent(previous) only resolves when a different
candidate appears, proving that a healthy unchanged connection leaves the promise pending.

packages/runtime-host/src/server/access-authority.ts[458-471]
apps/desktop/src/main/runtime-host-desktop-manager.ts[243-254]
apps/desktop/src/main/runtime-host-desktop-manager.ts[738-750]
packages/runtime-host/src/client/reconnect-lifecycle.ts[129-149]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A `commit_outcome_unknown` operation response does not imply connection loss, but finalization waits for a new reconnect candidate before retrying. If the connection stays healthy, onboarding hangs forever.

## Issue Context
Separate the two existing retry cases: a dispatched `connection_lost` error should wait for the reconnect lifecycle, while `commit_outcome_unknown` should retry idempotent finalization on the current candidate. No new public state or configuration is needed.

## Fix Focus Areas
- apps/desktop/src/main/runtime-host-desktop-manager.ts[243-253]
- apps/desktop/src/main/runtime-host-desktop-manager.ts[738-750]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View high (7)
4. Finalize failure strands replacement ✓ Resolved 🐞 Bug ☼ Reliability
Description
Fix-now: addAndEnableVerified() treats credential finalization as successful only after it has
already rebound and enabled the existing profile, but its failure path merely marks the replacement
unavailable and throws. A lost/failed finalize RPC can therefore make onboarding report failure
while the Desktop has discarded the prior saved credential and retains a pending credential that
later expires, leaving the previously usable Host disconnected until the user repeats setup.
Code

apps/desktop/src/main/runtime-host-profile-service.ts[R345-350]

+            await input.finalizePairing(profile.id);
+            unavailable.delete(profile.id);
+            return { profileId: profile.id };
+          } catch (error) {
+            unavailable.set(profile.id, asError(error));
+            throw error;
Relevance

●●● Strong

Recent reliability precedents accept preserving failure causes and transactional cleanup across
Runtime Host lifecycle boundaries.

PR-#2674
PR-#1755

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The replacement branch performs its rollback only around rebind, enable, and preference persistence;
finalization occurs afterward, outside that rollback. The authority accepts pending credentials only
until expiresAt, and finalize() can fail before changing the pending record to active, so a
failure at this point leaves the persisted candidate temporary while the prior Desktop credential
has already been overwritten.

apps/desktop/src/main/runtime-host-profile-service.ts[267-343]
apps/desktop/src/main/runtime-host-profile-service.ts[345-350]
packages/runtime-host/src/server/access-authority.ts[81-105]
packages/runtime-host/src/server/access-authority.ts[227-258]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A re-pair updates the existing Desktop profile and enables the candidate credential before finalization. If `finalizePairing` fails, the current code only records the failure, so the old saved credential has already been replaced while the candidate may remain pending and expire.

## Issue Context
Finalization is idempotent when the candidate is already active, but a persistence/RPC failure can leave it pending. Do not blindly restore the old profile without reconciling the remote finalization state, because a response may have been lost after the server committed.

## Fix Focus Areas
- apps/desktop/src/main/runtime-host-profile-service.ts[267-350]
- apps/desktop/src/main/runtime-host-desktop-manager.ts[234-245]
- packages/runtime-host/src/server/access-authority.ts[227-258]

Reconcile/retry the candidate finalization using the persisted candidate before reporting failure; if it is conclusively not finalized, restore the prior resolved profile/credential and manager target (or otherwise retain an explicit recoverable finalization state). Add a regression test for a re-pair whose finalization fails before server commit, asserting the existing profile remains usable rather than being left with an expiring pending credential.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Cancelled PTY can hang ✓ Resolved 🐞 Bug ☼ Reliability
Description
Fix-now: abort and the ten-minute timeout only send SIGTERM before awaiting process.exited
without forced termination or a rejection fallback. If SSH/SCP or its remote child does not exit,
onboarding remains active, later setup is prevented, and Cancel, application shutdown, and the
advertised setup timeout can all block indefinitely.
Code

apps/desktop/src/main/runtime-host-ssh-terminal.ts[R302-304]

+      const onAbort = () => process.kill('SIGTERM');
+      setupInput.signal?.addEventListener('abort', onAbort, { once: true });
+      const timeout = setTimeout(() => process.kill('SIGTERM'), SETUP_TIMEOUT_MS);
Relevance

●●● Strong

Unbounded PTY shutdown can block cancellation and shutdown; recent cleanup precedents favor explicit
termination handling.

PR-#3185

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Both setup SSH and development-archive SCP install cancellation or timeout callbacks that only send
SIGTERM, then await the PTY-backed exit promise without escalation or another bounded outcome. The
onboarding cancel and close handlers await the setup task, propagating the unbounded wait to UI
cancellation and shutdown, while the shared SSH tunnel implementation demonstrates a termination
policy for the same process abstraction that escalates to SIGKILL after two seconds.

apps/desktop/src/main/runtime-host-ssh-terminal.ts[302-324]
apps/desktop/src/main/runtime-host-ssh-terminal.ts[447-462]
apps/desktop/src/main/runtime-host-onboarding.ts[135-152]
packages/runtime-host/src/client/ssh-tunnel.ts[26-33]
packages/runtime-host/src/client/ssh-tunnel.ts[165-189]
apps/desktop/src/main/runtime-host-boot.ts[1338-1344]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Setup cancellation and the ten-minute timeout can hang forever because both the setup SSH and development-archive SCP paths send only `SIGTERM` and then wait indefinitely for `process.exited`. A stuck process can retain the active setup, prevent later setup, and delay application shutdown.

## Issue Context
Reuse the existing process kill/exited seam and SSH tunnel termination policy: send `SIGTERM`, allow a bounded grace period, and then issue `SIGKILL` while awaiting the exit promise. Apply this to both setup SSH and development-archive SCP processes, and ensure timeout produces a terminal error; the additional timer and branch are necessary because `SIGTERM` is advisory and the current exit promise has no bounded alternative.

## Fix Focus Areas
- apps/desktop/src/main/runtime-host-ssh-terminal.ts[302-324]
- apps/desktop/src/main/runtime-host-ssh-terminal.ts[447-462]
- apps/desktop/src/main/__tests__/runtime-host-ssh-terminal.test.ts[66-168]
- packages/runtime-host/src/client/ssh-tunnel.ts[165-189]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Version hash omits manifest ✓ Resolved 🐞 Bug ≡ Correctness
Description
Fix-now: developmentPackageVersion hashes stageRoot before writeReleaseManifest creates
package.json, even though that generated manifest is part of the packed archive. Changes to
generated metadata such as bin, engines, or publish/package fields can therefore produce
different archives with the same supposedly content-addressed development version, defeating the
cache-busting purpose of the override.
Code

scripts/release-cli-package.mjs[R516-521]

+  for (const path of walkFiles(stageRoot)
+    .filter((candidate) => lstatSync(candidate).isFile())
+    .sort()) {
+    digest.update(relative(stageRoot, path).split(sep).join('/'));
+    digest.update('\0');
+    digest.update(readFileSync(path));
Relevance

●●● Strong

The generated manifest is archived but omitted from its content hash, violating the stated
cache-busting invariant.

PR-#3188

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The build calls writeReleaseManifest after all copy steps, and that function computes the
development version before writing stageRoot/package.json. The omitted file contains behaviorally
relevant package metadata copied from both the CLI and root manifests, while npm pack includes that
file in the archive.

scripts/release-cli-package.mjs[109-123]
scripts/release-cli-package.mjs[458-524]
packages/cli/package.json[2-27]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The development version digest excludes the generated root package.json, so it is not a digest of all package content.

## Issue Context
Consolidate manifest construction and hashing: construct the manifest without its self-referential version, hash that canonical metadata together with staged files, then assign the derived version and write it. This does not require a new public surface or configuration; it only changes the ordering of the existing authority.

## Fix Focus Areas
- scripts/release-cli-package.mjs[458-524]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Failed re-pair overwrites profile ✓ Resolved 🐞 Bug ≡ Correctness
Description
Fix-now: the existing-root branch saves the new name and credential before attempting the
connection, but its failure handler never restores the prior target. A failed re-pair therefore
loses the prior saved credential/name and can also replace a previously active manager target with
an unavailable one even though onboarding reports failure.
Code

apps/desktop/src/main/runtime-host-profile-service.ts[R267-269]

+          const profile = { ...existing, name: value.profile.name };
+          await catalog.save(profile, value.credential);
+          const target = await catalog.resolve(profile.id);
Relevance

●●● Strong

Persisting replacement credentials before connection without restoring the prior target is a clear
rollback correctness defect.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
catalog.save updates the profile and credential before input.enable; the catch only records an
unavailable error and rethrows. RuntimeHostDesktopManager removes an existing same-ID target when
its resolved target changes before attempting the replacement connection, so failure also tears down
the old active target.

apps/desktop/src/main/runtime-host-profile-service.ts[262-297]
packages/runtime-host/src/client/host-profile.ts[362-400]
apps/desktop/src/main/runtime-host-desktop-manager.ts[339-371]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Refreshing an existing Host mutates its persisted profile before connection succeeds and leaves that mutation in place on failure.

## Issue Context
Consolidate this branch with the transactional rollback authority already used for newly created verified profiles. Preserve the previous resolved target, and on failure restore its profile/credential and reconnect it if it was enabled; no new persistent state is needed.

## Fix Focus Areas
- apps/desktop/src/main/runtime-host-profile-service.ts[262-297]
- apps/desktop/src/main/runtime-host-profile-service.ts[316-328]
- apps/desktop/src/main/__tests__/runtime-host-profile-service.test.ts[249-277]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Setup cancellation still persists host ✗ Dismissed 🐞 Bug ≡ Correctness
Description
Cancelling after remote setup completes but while addAndEnableVerified() is connecting or
persisting the profile does not stop the operation, so it can still enable and save a remote Host
and publish complete after cancellation. The dialog therefore reports cancellation while leaving a
computer added that the user explicitly aborted.
Code

apps/desktop/src/main/runtime-host-onboarding.ts[R96-99]

+      const connected = await input.profiles.addAndEnableVerified({
+        profile: {
+          id: profileId,
+          name: profileName,
Relevance

●●● Strong

Cancellation must cover the profile transaction and prevent completion after abort; this is a direct
correctness gap.

PR-#3048

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The onboarding signal is passed to runSetup and checked before profile work, but it is neither
passed into the newly added addAndEnableVerified() operation nor checked before the successful
path publishes completion. Because the cancellation IPC waits for this same task, an abort arriving
during the profile-service call cannot prevent enablement and persistence; the profile service has
no abort boundary for this transaction, even though the existing remote connection seam supports
AbortSignal.

apps/desktop/src/main/runtime-host-onboarding.ts[71-87]
apps/desktop/src/main/runtime-host-onboarding.ts[96-123]
apps/desktop/src/main/runtime-host-profile-service.ts[259-329]
packages/runtime-host/src/client/host-profile.ts[153-235]
apps/desktop/src/main/runtime-host-onboarding.ts[87-116]
apps/desktop/src/main/runtime-host-onboarding.ts[135-140]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Cancellation is checked after SSH setup but not while the verified profile is being connected, enabled, and persisted. Cancelling during that interval can leave a newly added Host enabled and saved, then publish completion despite the user's cancellation.

## Issue Context
Make the profile verification path participate in the existing onboarding abort lifecycle. A check after `addAndEnableVerified()` alone is insufficient because connection and persistence have already occurred, so extend the operation's cancellation contract and reuse the profile service's existing rollback/removal seams to compensate for any newly created profile when cancellation wins.

## Fix Focus Areas
- apps/desktop/src/main/runtime-host-onboarding.ts[87-116]
- apps/desktop/src/main/runtime-host-profile-service.ts[259-329]
- apps/desktop/src/main/__tests__/runtime-host-onboarding.test.ts[7-60]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Orphaned pairing credentials ✓ Resolved 🐞 Bug ⛨ Security
Description
Switching setup from replace to issue leaves the candidate credential active after the CLI has
emitted complete; if Desktop then cannot connect, persist, or finalize the profile, nothing
revokes that candidate. Repeated failed onboarding attempts accumulate fully privileged remote-owner
credentials for the same Desktop principal until an eventual successful finalization, and the access
file retains them indefinitely.
Code

packages/cli/src/runtime-host-setup-command.ts[R159-161]

+  let paired: Awaited<ReturnType<typeof issueRuntimeHostAccessCredential>>;
  try {
-    paired = await deps.replaceCredential({
+    paired = await deps.issueCredential({
Relevance

●● Moderate

Security cleanup concern is plausible; PR actually adds revoke-on-failure, but a similar
transactional-cleanup ask was rejected once.

PR-#3230

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed setup path creates a non-replacing active credential, then only revokes it when the
CLI's own verification throws. The access authority persists issued credentials as active, while
Desktop's subsequent profile-connection rollback removes only the local catalog entry; it has no
credential ID in its setup result with which to invoke cleanup.

packages/cli/src/runtime-host-setup-command.ts[159-207]
packages/runtime-host/src/server/access-authority.ts[120-145]
apps/desktop/src/main/runtime-host-profile-service.ts[354-379]
apps/desktop/src/main/runtime-host-onboarding.ts[95-110]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`runtime-host setup` now issues a candidate credential rather than replacing the prior credential. After its successful local verification and completion frame, Desktop can still fail while connecting, saving, or finalizing; that candidate remains active because only verification failures invoke the existing revoke path.

## Issue Context
Reuse the existing credential-revocation seam rather than adding an independent credential store or authority. Preserve the successful-pairing behavior: the candidate must remain usable until Desktop verifies and finalizes it, but it must be revoked when the Desktop-side pairing attempt fails. If cleanup cannot be reached, introduce explicit bounded candidate lifecycle state/expiry only if reusing the existing revoke path cannot guarantee cleanup; document and test that new lifecycle burden.

## Fix Focus Areas
- packages/cli/src/runtime-host-setup-command.ts[159-207]
- apps/desktop/src/main/runtime-host-profile-service.ts[354-388]
- apps/desktop/src/main/runtime-host-onboarding.ts[75-110]
- packages/cli/src/runtime-host-access-command.ts[138-151]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Stale credential revokes replacement ✓ Resolved 🐞 Bug ⛨ Security
Description
Fix-now: finalize() accepts any active credential and revokes every other active credential for
the same principal, so an older connection can finalize after a newer setup credential is issued and
invalidate that replacement. This breaks safe credential handoff and lets a stale or compromised
client prevent re-pairing.
Code

packages/runtime-host/src/server/access-authority.ts[R200-205]

+      const revoked = this.#file.credentials.filter(
+        (credential) =>
+          credential.credentialId !== credentialId &&
+          credential.status === 'active' &&
+          credential.principalKind === retained.principalKind &&
+          credential.principalId === retained.principalId,
Relevance

●● Moderate

Race condition is concrete but no closely matching accepted/rejected precedent establishes team's
stance on this exact issue.

PR-#3230

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Issued credentials are appended to the credential file, but finalization checks only that the caller
remains active and then selects all same-principal active siblings without considering issuance
order. The operation is available to remote owners and Desktop invokes it only after an asynchronous
connection wait, providing a concrete window for a newer credential to be issued before the stale
caller finalizes.

packages/runtime-host/src/server/access-authority.ts[96-169]
packages/runtime-host/src/server/access-authority.ts[192-219]
packages/runtime-host/src/protocol/operations.ts[200-206]
apps/desktop/src/main/runtime-host-desktop-manager.ts[234-245]
apps/desktop/src/main/runtime-host-profile-service.ts[344-350]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Credential finalization currently lets any active credential revoke all sibling credentials for its principal. Require the caller's credential to be the newest active issuance for that principal, and revoke only credentials that precede it.

## Issue Context
Reuse the access file's existing issuance ordering rather than adding a new protocol operation or public state: credentials are appended when issued. A stale credential must fail finalization if a later active credential for the same principal exists.

## Fix Focus Areas
- packages/runtime-host/src/server/access-authority.ts[192-219]
- packages/runtime-host/src/server/access-authority.ts[132-145]
- packages/runtime-host/src/__tests__/authenticated-websocket.test.ts[367-379]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

11. Stable version breaks CLI test 🐞 Bug ⚙ Maintainability ⭐ New
Description
Fix-now: the test now builds its fixture from the current CLI version but still hardcodes
dist_tag=next; when the manifest advances to a supported stable version, the command correctly
emits latest and this test fails. This makes a normal stable release require an unrelated test
rewrite.
Code

scripts/release-cli-publication.test.mjs[333]

+  const fixture = createCandidate(CURRENT_CLI_VERSION);
Relevance

●●● Strong

Clear deterministic test-fixture mismatch; recent accepted precedent favors eliminating brittle
future test failures.

PR-#3185
PR-#3188

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed fixture call uses CURRENT_CLI_VERSION, while the same assertion remains fixed to
dist_tag=next. The release parser explicitly maps versions without a prerelease component to
latest, and this test suite already asserts that stable versions are supported.

scripts/release-cli-publication.test.mjs[21-23]
scripts/release-cli-publication.test.mjs[332-358]
scripts/release-cli-publication.mjs[24-31]
scripts/release-cli-publication.test.mjs[25-35]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The prepare-stage CLI test uses the current package version but assumes that version is always a prerelease. A stable CLI version produces the valid `latest` distribution tag and causes the test to fail.

## Issue Context
Reuse the existing `parseCliReleaseVersion` seam to derive expected release identity. No new state, branch, configuration, or public surface is needed.

## Fix Focus Areas
- scripts/release-cli-publication.test.mjs[21-23]
- scripts/release-cli-publication.test.mjs[332-358]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. Build metadata package rejected ✓ Resolved 🐞 Bug ≡ Correctness
Description
runtimeHostSetupPackage() accepts exact package versions containing +, such as
maka-agent@1.2.3+build.1, but prepareSetupPackage() rejects the resulting specifier before SSH
setup runs. A Desktop manifest using valid SemVer build metadata therefore passes startup validation
but every Add computer attempt fails with “Runtime Host setup package is invalid.”
Code

apps/desktop/src/main/runtime-host-boot.ts[R325-327]

+    typeof specifier !== "string" ||
+    !/^maka-agent@[0-9][0-9A-Za-z.+-]*$/u.test(specifier)
+  ) {
Relevance

●●● Strong

Recent validation and protocol-boundary bugs are explicitly accepted; this is a deterministic
incompatible-regex correctness fix.

PR-#3230
PR-#3079

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The boot path accepts + and returns the specifier as an npm setup package, while the SSH
preparation path applies a narrower character class and throws before constructing the remote
command. These two validators govern the same value but define incompatible accepted sets.

apps/desktop/src/main/runtime-host-boot.ts[316-330]
apps/desktop/src/main/runtime-host-ssh-terminal.ts[440-458]
apps/desktop/src/main/runtime-host-ssh-terminal.ts[568-589]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Desktop manifest validator accepts `+` in an exact `maka-agent` version, while the SSH setup validator rejects it. This makes a package accepted at boot unusable during onboarding.

## Issue Context
Reuse one validator for both selection and setup-package preparation rather than adding another independent rule. The shared rule should accept the exact package-version syntax supported by the manifest contract, including SemVer build metadata.

## Fix Focus Areas
- apps/desktop/src/main/runtime-host-boot.ts[323-330]
- apps/desktop/src/main/runtime-host-ssh-terminal.ts[454-458]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. Failed setup leaves remote archive ✓ Resolved 🐞 Bug ☼ Reliability
Description
In unpackaged development mode, a successful SCP upload is cleaned up only by the subsequent remote
setup command. If SSH setup cannot start, is cancelled before launch, or cannot connect, that
command never runs and leaves the uploaded archive in the remote user's /tmp.
Code

apps/desktop/src/main/runtime-host-ssh-terminal.ts[R451-458]

+    const result = await process.exited;
+    signal?.throwIfAborted();
+    if (result.code !== 0) {
+      throw new Error(
+        `Uploading the Runtime Host development package exited with code ${String(result.code)}`,
+      );
+    }
+    return { specifier: remoteArchive, removeAfterSetup: remoteArchive };
Relevance

●●● Strong

Temporary remote artifacts should be cleaned on every failure path, not only after successful SSH
setup.

PR-#3185

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
After SCP succeeds, the uploaded path is returned for later cleanup. The only removal is embedded in
the SSH remote command, which is launched afterward; therefore failure before or during SSH launch
cannot execute it.

apps/desktop/src/main/runtime-host-ssh-terminal.ts[431-458]
apps/desktop/src/main/runtime-host-ssh-terminal.ts[261-299]
apps/desktop/src/main/runtime-host-ssh-terminal.ts[486-489]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A development package uploaded to the remote temporary directory is removed only within the normal remote setup command. Failures between upload completion and that command leave the archive behind.

## Issue Context
This affects only the explicit unpackaged-development archive override, so it is non-blocking, but it violates the temporary-upload expectation and accumulates remote files after retries.

## Fix Focus Areas
- apps/desktop/src/main/runtime-host-ssh-terminal.ts[431-462]
- apps/desktop/src/main/runtime-host-ssh-terminal.ts[465-489]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: ⚖️ Balanced: This small push changes desktop shutdown ordering and suppresses an async UI failure, so its behavioral lifecycle effects merit a full review.

Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread apps/desktop/src/main/runtime-host-ssh-terminal.ts Outdated
Comment thread apps/desktop/src/main/runtime-host-profile-service.ts Outdated
Comment thread scripts/release-cli-package.mjs
Comment thread apps/desktop/src/main/runtime-host-onboarding.ts
@M4n5ter
M4n5ter force-pushed the feat/desktop-managed-remote-onboarding branch from 710be23 to e04eb16 Compare August 19, 2026 06:21
@M4n5ter

M4n5ter commented Aug 19, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

Comment thread packages/runtime-host/src/server/access-authority.ts
Comment thread packages/cli/src/runtime-host-setup-command.ts Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 56ff859

@M4n5ter
M4n5ter force-pushed the feat/desktop-managed-remote-onboarding branch from 56ff859 to 9c4737a Compare August 19, 2026 07:24
@M4n5ter

M4n5ter commented Aug 19, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

Comment thread apps/desktop/src/main/runtime-host-profile-service.ts Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit a654cba

@M4n5ter
M4n5ter force-pushed the feat/desktop-managed-remote-onboarding branch from c4082fc to c4a54fd Compare August 19, 2026 08:49
@M4n5ter

M4n5ter commented Aug 19, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

Comment thread apps/desktop/src/main/runtime-host-desktop-manager.ts Outdated
Comment thread apps/desktop/src/main/runtime-host-boot.ts Outdated
Comment thread apps/desktop/src/renderer/app-shell.tsx Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 37ec416

@M4n5ter
M4n5ter force-pushed the feat/desktop-managed-remote-onboarding branch from 49996da to b2543c8 Compare August 19, 2026 10:14
@M4n5ter
M4n5ter marked this pull request as ready for review August 19, 2026 10:18

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (8)
packages/cli/src/runtime-host-cli.ts (1)

184-208: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Make allowConfiguration default explicit.

allowConfiguration is optional and the guard tests === false. A caller that omits the field gets configuration arguments enabled. parseSetupCommand relies on that implicit default, while parseServiceManagementCommand passes the value explicitly. A future caller that forgets the field silently accepts --root, --project-root, --websocket-port, and --websocket-path.

Disposition: optional. Destructure with a default so the intent is stated at one place.

♻️ Proposed refactor
 function parseManagedServiceOptions(
   argv: string[],
-  input: {
+  {
+    valueOptions,
+    flagOptions,
+    allowConfiguration = true,
+  }: {
     readonly valueOptions?: Readonly<Record<string, (value: string) => RuntimeHostCliError | void>>;
     readonly flagOptions?: Readonly<Record<string, () => RuntimeHostCliError | void>>;
     readonly allowConfiguration?: boolean;
   } = {},
 ): ManagedServiceOptions | RuntimeHostCliError {

Then use flagOptions, valueOptions, and if (!allowConfiguration) in the loop.

packages/cli/src/__tests__/runtime-host-service-manager.test.ts (1)

89-97: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add coverage for the duplicate --defer-pairing-commit branch.

parseSetupCommand in packages/cli/src/runtime-host-cli.ts (Line 127) returns Duplicate --defer-pairing-commit when the flag repeats. This test only covers the single-use case. The rejection branch has no assertion here.

Disposition: optional. Add one case that passes the flag twice and asserts the error result.

packages/runtime-host/src/server/operation-dispatcher.ts (1)

239-277: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: collapse the five identical unavailable handlers.

Every entry returns the same outcome. A single factory removes the repetition and keeps the Pick type check intact.

♻️ Proposed refactor
+const accessCredentialsUnavailable = async () => ({
+  ok: false as const,
+  error: {
+    code: 'operation_unavailable' as const,
+    message: 'Runtime Host access credentials are unavailable',
+  },
+});
+
 export function createUnavailableAccessAuthorityOperationHandlers(): AccessAuthorityOperationHandlerMap {
   return {
-    'access.credential.issue': async () => ({ ... }),
-    'access.credential.replace': async () => ({ ... }),
-    'access.credential.prepare': async () => ({ ... }),
-    'access.credential.revoke': async () => ({ ... }),
-    'access.credential.finalize': async () => ({ ... }),
+    'access.credential.issue': accessCredentialsUnavailable,
+    'access.credential.replace': accessCredentialsUnavailable,
+    'access.credential.prepare': accessCredentialsUnavailable,
+    'access.credential.revoke': accessCredentialsUnavailable,
+    'access.credential.finalize': accessCredentialsUnavailable,
   };
 }

Disposition: optional.

Source: Path instructions

packages/runtime-host/src/__tests__/host-profile.test.ts (1)

170-196: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the credential-only rebind path.

The test only exercises bindingChanged === true (the transport URL changes). The bindingChanged === false branch in rebindIfCurrent behaves differently: it reuses the same credential slot, sets displacedCredential to target.credential, and skips the credentials.delete(stored) step. That branch is unverified.

The profile.id/profile.rootId guard rejection is also unverified.

💚 Suggested additional assertions
+  test('rebinds a credential without changing the transport binding', async () => {
+    const path = await profilePath();
+    const credentials = memoryCredentials();
+    const desktop = createFileRuntimeHostProfileCatalog(path, credentials);
+    const profile = remoteProfile('office', 'wss://host.example.com', ROOT_A);
+    await desktop.create(profile, 'old-token');
+    const expected = await desktop.resolve(profile.id);
+    assert.equal((await desktop.rebindIfCurrent(expected, profile, 'new-token')).rebound, true);
+    assert.equal((await desktop.resolve(profile.id)).credential, 'new-token');
+  });
+
+  test('rejects a rebind that changes the Host identity', async () => {
+    const path = await profilePath();
+    const credentials = memoryCredentials();
+    const desktop = createFileRuntimeHostProfileCatalog(path, credentials);
+    const profile = remoteProfile('office', 'wss://host.example.com', ROOT_A);
+    await desktop.create(profile, 'old-token');
+    const expected = await desktop.resolve(profile.id);
+    await assert.rejects(
+      desktop.rebindIfCurrent(expected, remoteProfile('office', 'wss://host.example.com', ROOT_B), 'x'),
+      /must retain its Host identity/,
+    );
+  });

Disposition: follow-up.

Source: Path instructions

packages/cli/src/runtime-host-access-command.ts (1)

79-85: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: give the exported functions explicit, consistent signatures.

Two small inconsistencies in the new public surface:

  • Line 79: replaceRuntimeHostAccessCredential is async but only forwards a promise, while issueRuntimeHostAccessCredential and prepareRuntimeHostAccessCredential are not async.
  • Line 146: revokeRuntimeHostAccessCredential has no declared return type. runtime-host-setup-command.ts binds to it through typeof, so the exported contract is inferred from connection.request internals.
♻️ Proposed change
-export async function replaceRuntimeHostAccessCredential(
+export function replaceRuntimeHostAccessCredential(
   options: RuntimeHostAccessIssueOptions,
 ): Promise<ReplacedRuntimeHostAccessCredential> {
   return mutateRuntimeHostAccessCredential(options, 'access.credential.replace');
 }
-export async function revokeRuntimeHostAccessCredential(options: RuntimeHostAccessRevokeOptions) {
+export async function revokeRuntimeHostAccessCredential(
+  options: RuntimeHostAccessRevokeOptions,
+): Promise<OperationOutput<'access.credential.revoke'>> {

Disposition: optional.

Also applies to: 146-146

Source: Path instructions

packages/cli/src/runtime-host-managed-deployment.ts (1)

219-240: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Pass versionsRoot explicitly to pruneInactiveDevelopmentPackages. This avoids recomputing it with dirname(packageRoot) and removes unnecessary indirection.

Source: Path instructions

apps/desktop/src/main/__tests__/runtime-host-ssh-terminal.test.ts (1)

181-181: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Delete the shell-variable-name assertion.

assert.doesNotMatch(remoteCommand, /\bstatus=/u) asserts the internal name of a shell variable in the generated command. That name has no observable effect. The adjacent assertion on /maka_setup_exit/u already covers the exit-code propagation contract. A rename would break this test without any behavior change.

🧹 Suggested removal
   assert.match(remoteCommand, /maka_setup_exit/u);
-  assert.doesNotMatch(remoteCommand, /\bstatus=/u);

As per path instructions: "Flag tests that duplicate existing coverage, assert implementation details, or do not protect observable behavior."

Source: Path instructions

apps/desktop/src/main/__tests__/runtime-host-onboarding.test.ts (1)

62-127: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the cancellable path and for endpoint rejection.

The tests cover the non-cancellable commit phase and the success path. Two behaviors in runtime-host-onboarding.ts remain untested: cancel during the SSH phase returns true and publishes idle, and requireSetupEndpoint rejects a non-loopback or non-ws: endpoint. Both are security-relevant guards.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 35a99f27-6640-4c0a-9bbf-fdc85c2bf176

📥 Commits

Reviewing files that changed from the base of the PR and between b03b85e and b2543c8.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (56)
  • CONTRIBUTING.md
  • CONTRIBUTING.zh-CN.md
  • apps/desktop/electron-builder.config.mjs
  • apps/desktop/src/main/__tests__/runtime-host-desktop-manager.test.ts
  • apps/desktop/src/main/__tests__/runtime-host-onboarding.test.ts
  • apps/desktop/src/main/__tests__/runtime-host-profile-service.test.ts
  • apps/desktop/src/main/__tests__/runtime-host-ssh-terminal.test.ts
  • apps/desktop/src/main/runtime-host-boot.ts
  • apps/desktop/src/main/runtime-host-client.ts
  • apps/desktop/src/main/runtime-host-desktop-manager.ts
  • apps/desktop/src/main/runtime-host-onboarding.ts
  • apps/desktop/src/main/runtime-host-profile-service.ts
  • apps/desktop/src/main/runtime-host-ssh-terminal.ts
  • apps/desktop/src/preload/bridge-contract.d.ts
  • apps/desktop/src/preload/preload.ts
  • apps/desktop/src/renderer/app-shell-overlays.tsx
  • apps/desktop/src/renderer/app-shell.tsx
  • apps/desktop/src/renderer/locales/settings-projects-copy.ts
  • apps/desktop/src/renderer/settings/projects-settings-page.tsx
  • apps/desktop/src/renderer/settings/runtime-host-onboarding-dialog.tsx
  • apps/desktop/src/renderer/settings/runtime-host-profiles-section.tsx
  • apps/desktop/src/renderer/settings/settings-modal.tsx
  • apps/desktop/src/renderer/settings/settings-surface.tsx
  • apps/desktop/src/renderer/styles/settings/runtime-host.css
  • apps/desktop/src/renderer/use-new-task-target.ts
  • apps/desktop/stories/settings/settings-pages.stories.tsx
  • docs/astryx-surface-file-inventory.md
  • docs/astryx-surface-file-inventory.paths
  • docs/runtime-host-remote-access.md
  • docs/runtime-host-remote-access.zh-CN.md
  • packages/cli/package.json
  • packages/cli/src/__tests__/runtime-host-cli-context.test.ts
  • packages/cli/src/__tests__/runtime-host-operator-command.test.ts
  • packages/cli/src/__tests__/runtime-host-profile-command.test.ts
  • packages/cli/src/__tests__/runtime-host-service-manager.test.ts
  • packages/cli/src/__tests__/runtime-host-setup.test.ts
  • packages/cli/src/cli-core.ts
  • packages/cli/src/runtime-host-access-command.ts
  • packages/cli/src/runtime-host-cli.ts
  • packages/cli/src/runtime-host-managed-deployment.ts
  • packages/cli/src/runtime-host-setup-command.ts
  • packages/cli/src/runtime-host-systemd-service.ts
  • packages/runtime-host/src/__tests__/authenticated-websocket.test.ts
  • packages/runtime-host/src/__tests__/host-profile.test.ts
  • packages/runtime-host/src/__tests__/websocket-listener.test.ts
  • packages/runtime-host/src/client/host-profile.ts
  • packages/runtime-host/src/protocol/access-authority.ts
  • packages/runtime-host/src/protocol/index.ts
  • packages/runtime-host/src/protocol/operations.ts
  • packages/runtime-host/src/server/access-authority.ts
  • packages/runtime-host/src/server/access-credential-store.ts
  • packages/runtime-host/src/server/connection-session.ts
  • packages/runtime-host/src/server/host-kernel.ts
  • packages/runtime-host/src/server/operation-dispatcher.ts
  • scripts/release-cli-package.mjs
  • scripts/release-cli-publication.test.mjs

Included review availability: Your plan provides up to 3 included reviews per hour; 1 remains after this review.

Comment thread apps/desktop/src/main/runtime-host-ssh-terminal.ts
Comment thread apps/desktop/src/preload/bridge-contract.d.ts
Comment thread apps/desktop/src/renderer/app-shell.tsx
Comment thread packages/runtime-host/src/server/access-authority.ts Outdated
Comment thread scripts/release-cli-package.mjs
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add managed remote Runtime Host onboarding to Desktop

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Adds one-destination SSH onboarding for installing, pairing, verifying, and connecting Linux
 Runtime Hosts.
• Makes credential handoff transactional and keeps secrets within Desktop’s main process.
• Supports content-addressed development archives and opens remote Project selection after setup.
Diagram

sequenceDiagram
  actor U as User
  participant R as Onboarding UI
  participant M as Desktop Main
  participant S as SSH Terminal
  participant C as Remote CLI
  participant H as Runtime Host
  participant P as Profile Manager
  participant J as Project Picker
  U->>R: Enter SSH target
  R->>M: Start onboarding
  M->>S: Run managed setup
  S->>C: Upload or fetch package
  C->>H: Install and prepare pairing
  H-->>C: Pending credential
  C-->>S: Setup progress frames
  S-->>M: Verified setup result
  M->>P: Persist and connect profile
  P->>H: Finalize authenticated pairing
  H-->>P: Activate new credential
  P-->>M: Profile ready
  M-->>R: Complete with profile ID
  R->>J: Open remote Project picker
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Implement setup directly in Desktop
  • ➕ Could tailor every installation step and error message to the Desktop UI.
  • ➕ Would avoid requiring the remote release CLI to expose a setup contract.
  • ➖ Duplicates service installation, deployment, and credential logic already owned by the CLI.
  • ➖ Expands Desktop’s security-sensitive SSH scripting and creates behavior drift between setup paths.
2. Deploy a persistent bootstrap agent
  • ➕ Could provide richer progress reporting and future remote management operations.
  • ➕ Would reduce repeated shell orchestration after initial installation.
  • ➖ Introduces another privileged lifecycle, protocol, and update surface on remote machines.
  • ➖ Requires users to preinstall or trust an additional service before onboarding.

Recommendation: Keep the PR’s approach: reuse the released CLI setup contract over interactive SSH, stage credential replacement, and finalize only through the verified Desktop connection. This minimizes duplicated installation logic, keeps secrets out of the renderer, and preserves manual profiles for unsupported endpoints; a persistent bootstrap agent is not justified for this onboarding scope.

Files changed (57) +2692 / -163

Enhancement (25) +1305 / -60
runtime-host-boot.tsWire managed onboarding into Desktop startup +44/-1

Wire managed onboarding into Desktop startup

• Creates the onboarding service, selects an exact release package or development archive, delegates pairing finalization, and closes onboarding during shutdown.

apps/desktop/src/main/runtime-host-boot.ts

runtime-host-client.tsExpose credential finalization on Host clients +4/-0

Expose credential finalization on Host clients

• Adds a typed client method for the new access credential finalization operation.

apps/desktop/src/main/runtime-host-client.ts

runtime-host-onboarding.tsAdd main-process onboarding coordinator +203/-0

Add main-process onboarding coordinator

• Adds validated IPC state management for starting, monitoring, cancelling, and resetting SSH onboarding. It persists verified profiles while keeping credentials out of renderer snapshots.

apps/desktop/src/main/runtime-host-onboarding.ts

runtime-host-ssh-terminal.tsRun managed setup over bounded SSH +448/-32

Run managed setup over bounded SSH

• Runs exact CLI setup packages through interactive SSH, optionally uploads development archives, parses hidden setup frames, and bounds cancellation and timeout termination. Setup becomes non-cancellable after the completion frame commits.

apps/desktop/src/main/runtime-host-ssh-terminal.ts

bridge-contract.d.tsDefine the onboarding bridge contract +41/-0

Define the onboarding bridge contract

• Adds onboarding input, phase, snapshot, command, and subscription types to the renderer bridge.

apps/desktop/src/preload/bridge-contract.d.ts

preload.tsExpose onboarding IPC to the renderer +24/-0

Expose onboarding IPC to the renderer

• Adds preload methods for onboarding snapshots, start, cancel, reset, and change subscriptions.

apps/desktop/src/preload/preload.ts

app-shell-overlays.tsxRoute remote Host completion through overlays +2/-0

Route remote Host completion through overlays

• Passes the newly added remote profile callback into the settings modal.

apps/desktop/src/renderer/app-shell-overlays.tsx

app-shell.tsxOpen Project selection after onboarding +5/-0

Open Project selection after onboarding

• Closes settings, opens the new-task surface, and selects the newly connected Host for remote Project selection.

apps/desktop/src/renderer/app-shell.tsx

settings-projects-copy.tsAdd bilingual onboarding copy +57/-2

Add bilingual onboarding copy

• Adds English and Simplified Chinese labels for managed setup controls, progress phases, completion, and manual configuration.

apps/desktop/src/renderer/locales/settings-projects-copy.ts

projects-settings-page.tsxConnect settings to onboarding completion +3/-2

Connect settings to onboarding completion

• Passes the remote Host completion callback into the Runtime Host profiles section in both availability states.

apps/desktop/src/renderer/settings/projects-settings-page.tsx

runtime-host-onboarding-dialog.tsxAdd the remote computer onboarding dialog +193/-0

Add the remote computer onboarding dialog

• Provides SSH destination and port inputs, live setup phases, cancellation and retry handling, and completion actions for choosing a remote Project.

apps/desktop/src/renderer/settings/runtime-host-onboarding-dialog.tsx

runtime-host-profiles-section.tsxAdd managed and manual Host actions +29/-8

Add managed and manual Host actions

• Introduces a primary Add computer flow while retaining manual profile configuration for advanced endpoints. Reloads profiles after onboarding and forwards Project selection.

apps/desktop/src/renderer/settings/runtime-host-profiles-section.tsx

settings-modal.tsxForward remote Host completion from settings +2/-0

Forward remote Host completion from settings

• Adds and forwards the remote Host completion callback through the settings modal.

apps/desktop/src/renderer/settings/settings-modal.tsx

settings-surface.tsxPropagate onboarding completion through settings +4/-0

Propagate onboarding completion through settings

• Threads the remote Host completion callback through the settings surface and Projects page.

apps/desktop/src/renderer/settings/settings-surface.tsx

runtime-host.cssStyle onboarding progress and actions +8/-0

Style onboarding progress and actions

• Adds spacing for multi-button terminal actions and lays out the managed setup progress indicator.

apps/desktop/src/renderer/styles/settings/runtime-host.css

use-new-task-target.tsSelect a newly connected Host +17/-0

Select a newly connected Host

• Refreshes the Host catalog, selects the ready profile, and opens its directory picker or reports unavailable state.

apps/desktop/src/renderer/use-new-task-target.ts

cli-core.tsForward deferred pairing into setup +1/-0

Forward deferred pairing into setup

• Passes the parsed deferred pairing option into the Runtime Host setup command.

packages/cli/src/cli-core.ts

runtime-host-cli.tsParse deferred pairing setup mode +36/-15

Parse deferred pairing setup mode

• Adds the defer-pairing-commit flag and refactors managed service option parsing to support value and boolean extensions.

packages/cli/src/runtime-host-cli.ts

runtime-host-managed-deployment.tsManage content-addressed development deployments +23/-0

Manage content-addressed development deployments

• Recognizes development package versions, adds deployment commit handling, and prunes superseded development versions after successful setup.

packages/cli/src/runtime-host-managed-deployment.ts

host-profile.tsAdd conditional Host profile rebinding +84/-0

Add conditional Host profile rebinding

• Atomically updates a profile transport and credential only when its expected profile and secret remain current, with rollback across file and credential stores.

packages/runtime-host/src/client/host-profile.ts

access-authority.tsDefine staged pairing protocol operations +40/-0

Define staged pairing protocol operations

• Adds credential prepare and finalize operations, strict payload decoding, and an explicit unknown commit outcome error.

packages/runtime-host/src/protocol/access-authority.ts

operations.tsAllow remote owners to finalize pairing +1/-0

Allow remote owners to finalize pairing

• Grants remote-owner credentials access to the narrowly scoped credential finalization operation.

packages/runtime-host/src/protocol/operations.ts

connection-session.tsAttach credentials to operation context +3/-0

Attach credentials to operation context

• Projects the authenticated credential identifier into dispatch context so finalization applies to the calling credential.

packages/runtime-host/src/server/connection-session.ts

host-kernel.tsDispatch and close staged access authority +18/-0

Dispatch and close staged access authority

• Registers credential prepare and finalize handlers, drains after uncertain commits, and closes the authority across startup failure and shutdown paths.

packages/runtime-host/src/server/host-kernel.ts

operation-dispatcher.tsExtend access operation dispatch context +15/-0

Extend access operation dispatch context

• Adds the authenticated credential identifier and unavailable handlers for credential preparation and finalization.

packages/runtime-host/src/server/operation-dispatcher.ts

Bug fix (6) +482 / -42
runtime-host-desktop-manager.tsFinalize pairing through the connected profile +42/-1

Finalize pairing through the connected profile

• Finalizes staged credentials over the authenticated remote connection and retries only when dispatch or commit outcomes are uncertain.

apps/desktop/src/main/runtime-host-desktop-manager.ts

runtime-host-profile-service.tsPersist managed profiles transactionally +140/-0

Persist managed profiles transactionally

• Adds verified profile creation and same-Host rebinding with connection checks, pairing finalization, and rollback of profiles, credentials, preferences, and enabled connections.

apps/desktop/src/main/runtime-host-profile-service.ts

runtime-host-setup-command.tsStage and verify setup credentials +51/-16

Stage and verify setup credentials

• Optionally prepares a pending credential, verifies it before reporting completion, revokes failed candidates, and commits successful deployments. Development versions may replace one another through the setup path.

packages/cli/src/runtime-host-setup-command.ts

runtime-host-systemd-service.tsKeep managed Hosts continuously running +1/-1

Keep managed Hosts continuously running

• Changes the systemd restart policy from failure-only to always, preserving service availability after clean exits.

packages/cli/src/runtime-host-systemd-service.ts

access-authority.tsImplement expiring staged credentials +218/-22

Implement expiring staged credentials

• Adds pending credential preparation, authenticated idempotent finalization, prior credential revocation, expiry scheduling, and authority shutdown. Persistence ambiguity is surfaced separately for safe client retry.

packages/runtime-host/src/server/access-authority.ts

access-credential-store.tsPersist pending credential state safely +30/-2

Persist pending credential state safely

• Extends stored credentials with pending status and expiry validation. Detects durability failures after publication so finalization can report an unknown commit outcome.

packages/runtime-host/src/server/access-credential-store.ts

Refactor (1) +36 / -27
runtime-host-access-command.tsGeneralize credential mutation helpers +36/-27

Generalize credential mutation helpers

• Extracts reusable issue, prepare, replace, and revoke helpers so managed setup can stage and roll back pairing credentials.

packages/cli/src/runtime-host-access-command.ts

Tests (14) +801 / -28
runtime-host-desktop-manager.test.tsTest pairing finalization retries +76/-0

Test pairing finalization retries

• Covers replay after an unknown finalization outcome and verifies that commands rejected before dispatch are not replayed.

apps/desktop/src/main/tests/runtime-host-desktop-manager.test.ts

runtime-host-onboarding.test.tsTest managed onboarding orchestration +127/-0

Test managed onboarding orchestration

• Verifies profile creation without renderer credential exposure and ensures cancellation is fenced once pairing commit begins.

apps/desktop/src/main/tests/runtime-host-onboarding.test.ts

runtime-host-profile-service.test.tsTest transactional managed profile persistence +148/-0

Test transactional managed profile persistence

• Covers failed profile cleanup, same-Host rebinding, pairing finalization, and restoration of previous profiles after connection or finalization failures.

apps/desktop/src/main/tests/runtime-host-profile-service.test.ts

runtime-host-ssh-terminal.test.tsTest secure and bounded SSH setup +170/-4

Test secure and bounded SSH setup

• Tests setup-frame filtering, credential secrecy, forced process termination, and development archive upload and reuse.

apps/desktop/src/main/tests/runtime-host-ssh-terminal.test.ts

settings-pages.stories.tsxUpdate settings story callback contract +1/-0

Update settings story callback contract

• Supplies the new remote Host completion callback in the settings story harness.

apps/desktop/stories/settings/settings-pages.stories.tsx

runtime-host-cli-context.test.tsUpdate profile catalog test double +3/-0

Update profile catalog test double

• Adds the conditional rebind method required by the expanded profile catalog contract.

packages/cli/src/tests/runtime-host-cli-context.test.ts

runtime-host-operator-command.test.tsTest credential preparation grants +2/-0

Test credential preparation grants

• Verifies that ordinary remote credentials cannot prepare credentials while local operators receive the new authority.

packages/cli/src/tests/runtime-host-operator-command.test.ts

runtime-host-profile-command.test.tsUpdate profile command test catalog +1/-0

Update profile command test catalog

• Adds a failing rebind stub to ensure profile commands do not unexpectedly use the new mutation path.

packages/cli/src/tests/runtime-host-profile-command.test.ts

runtime-host-service-manager.test.tsTest deferred pairing and service restart policy +3/-0

Test deferred pairing and service restart policy

• Covers parsing the deferred commit flag and verifies managed systemd services always restart.

packages/cli/src/tests/runtime-host-service-manager.test.ts

runtime-host-setup.test.tsTest staged setup and development replacement +99/-3

Test staged setup and development replacement

• Tests candidate revocation after verification failure and safe replacement and pruning of content-addressed development deployments.

packages/cli/src/tests/runtime-host-setup.test.ts

authenticated-websocket.test.tsTest transactional credential pairing +135/-18

Test transactional credential pairing

• Exercises pending credential authentication and idempotent finalization, old credential revocation, expiry cleanup, and safe access-authority shutdown.

packages/runtime-host/src/tests/authenticated-websocket.test.ts

host-profile.test.tsTest conditional profile rebinding +28/-0

Test conditional profile rebinding

• Verifies atomic transport and credential rebinding and rejects stale mutations after concurrent profile changes.

packages/runtime-host/src/tests/host-profile.test.ts

websocket-listener.test.tsUpdate access authority test contract +3/-0

Update access authority test contract

• Adds preparation, finalization, and closure stubs to the WebSocket listener authority fixture.

packages/runtime-host/src/tests/websocket-listener.test.ts

release-cli-publication.test.mjsAlign publication fixtures with the CLI version +5/-3

Align publication fixtures with the CLI version

• Uses the current CLI manifest version where the publication workflow requires exact package metadata while retaining overridable fixtures.

scripts/release-cli-publication.test.mjs

Documentation (6) +29 / -3
CONTRIBUTING.mdDocument development archive onboarding +10/-0

Document development archive onboarding

• Documents how to build a release-shaped CLI archive and use it for Desktop remote setup testing. Clarifies that packaged builds ignore the development override.

CONTRIBUTING.md

CONTRIBUTING.zh-CN.mdDocument development onboarding in Chinese +10/-0

Document development onboarding in Chinese

• Adds the Chinese instructions for building and uploading a worktree CLI archive during Desktop development.

CONTRIBUTING.zh-CN.md

astryx-surface-file-inventory.mdRegister the onboarding design surface +2/-1

Register the onboarding design surface

• Adds the onboarding dialog to the Astryx inventory and updates aligned file totals.

docs/astryx-surface-file-inventory.md

astryx-surface-file-inventory.pathsTrack the onboarding dialog path +1/-0

Track the onboarding dialog path

• Adds the new settings dialog to the generated design-surface path inventory.

docs/astryx-surface-file-inventory.paths

runtime-host-remote-access.mdDocument managed Desktop connection setup +3/-1

Document managed Desktop connection setup

• Replaces the manual-first instructions with Add computer onboarding and identifies manual configuration as the advanced endpoint path.

docs/runtime-host-remote-access.md

runtime-host-remote-access.zh-CN.mdDocument managed setup in Chinese +3/-1

Document managed setup in Chinese

• Updates the Chinese remote access guide for SSH onboarding and retained manual endpoint configuration.

docs/runtime-host-remote-access.zh-CN.md

Other (5) +39 / -3
electron-builder.config.mjsPin packaged onboarding to the CLI release +16/-0

Pin packaged onboarding to the CLI release

• Validates the CLI package identity during Desktop packaging and embeds its exact package specifier in application metadata.

apps/desktop/electron-builder.config.mjs

package-lock.jsonLock CLI beta version update +1/-1

Lock CLI beta version update

• Updates the workspace lock entry for Maka CLI 0.1.0-beta.2.

package-lock.json

package.jsonBump the CLI beta release +1/-1

Bump the CLI beta release

• Advances the CLI package version to 0.1.0-beta.2 for the onboarding-compatible release.

packages/cli/package.json

index.tsAdvance Runtime Host compatibility epoch +1/-1

Advance Runtime Host compatibility epoch

• Bumps the compatibility epoch for the new credential pairing protocol requirements.

packages/runtime-host/src/protocol/index.ts

release-cli-package.mjsContent-address development CLI packages +20/-0

Content-address development CLI packages

• Derives non-publishable package versions from staged file and manifest content so development archives can be replaced and identified safely.

scripts/release-cli-package.mjs

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit b2543c8

@Astro-Han Astro-Han 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.

Thanks for the substantial hardening here. The current head is green and the supplied screenshot covers the new connection flow, but two P2 correctness gaps remain: generated development archives are not recognized by the new version predicate, and a process exit after durable profile mutation but before credential finalization leaves pairing unrecoverable. The final substantive commit also needs the declared Codex Generated-by trailer. Please address these items, then request an exact-head rereview. Because this changes user-visible behavior, a public protocol, credential security, and release behavior, independent human review is still required by project policy.

Reviewed with Codex as an AI-assisted code review. I verified the current diff, relevant authority boundaries, tests, CI, screenshot, and commit provenance; no external model output was used.

中文说明

当前 head 的 CI 和截图都没有问题,但仍有两个 P2:新生成的 dev 版本与识别正则不一致;Profile 已持久化但 credential 尚未 finalize 时如果进程退出,重启后无法恢复,旧 Profile 甚至会丢失可用凭证。最后一个实质性 commit 还缺少已声明的 Codex Generated-by trailer。修复后请按新 head 重新 review;同时本 PR 涉及 UI、公开协议、credential security 和 release 行为,仍需独立人工审查。

Comment thread packages/cli/src/runtime-host-managed-deployment.ts Outdated
Comment thread apps/desktop/src/main/runtime-host-profile-service.ts Outdated

@Astro-Han Astro-Han 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.

An independent adversarial pass confirmed the two earlier P2 findings and found one additional user-visible cancellation gap, inline below. The rest of the prior review remains unchanged.

中文说明

独立交叉复核确认了前两个 P2,并额外发现一个用户可见的取消路径问题,见下面 inline。此前其余结论不变。

Comment thread apps/desktop/src/main/runtime-host-ssh-terminal.ts
@M4n5ter
M4n5ter force-pushed the feat/desktop-managed-remote-onboarding branch from 5f6bb39 to 0673602 Compare August 19, 2026 12:51

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
apps/desktop/src/main/__tests__/runtime-host-profile-service.test.ts (1)

336-359: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that rollback restores the previous enablement state.

The test verifies the credential is restored and the journal is cleared. It does not verify the intent.wasEnabled branch in rollbackPairingIntent at apps/desktop/src/main/runtime-host-profile-service.ts Lines 339-354. stageInterruptedPairing enables PROFILE.id, so rollback must re-add it to enabledRemoteProfileIds and call input.enable with the restored target.

Both effects can regress without failing this test. Add assertions on the persisted preferences and on the re-enable call.

💚 Sketch of the added assertions
   const service = createDesktopRuntimeHostProfileService({
     clientDataRoot: root,
     startup,
     catalog,
     states: () => [connectingLocal()],
     enable: async (target) => {
+      enabled.push(target.credential ?? "");
       if (target.credential === "new-token") {
         throw new RuntimeHostPermanentReconnectError("pairing credential expired");
       }
     },
   assert.equal((await catalog.resolve(PROFILE.id)).credential, "old-token");
+  assert.deepEqual(enabled, ["new-token", "old-token"]);
+  const restored = await resolveDesktopRuntimeHostStartup(root, { catalog });
+  assert.deepEqual(restored.preferences.enabledRemoteProfileIds, [PROFILE.id]);
-  assert.equal((await resolveDesktopRuntimeHostStartup(root, { catalog })).pairingIntents.length, 0);
+  assert.equal(restored.pairingIntents.length, 0);

Declare const enabled: string[] = []; above the service.

packages/cli/src/__tests__/runtime-host-setup.test.ts (1)

178-179: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the dot-separated development version.

The packager emits 0.1.0-beta.2.dev-<hex> when the base version contains -. Add a fixture with this form to cover the \. branch in isRuntimeHostDevelopmentPackageVersion during replacement and pruning.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0487ef76-0312-44fe-bf07-5d8dfceca9c6

📥 Commits

Reviewing files that changed from the base of the PR and between 5f6bb39 and 0673602.

📒 Files selected for processing (13)
  • apps/desktop/src/main/__tests__/runtime-host-onboarding.test.ts
  • apps/desktop/src/main/__tests__/runtime-host-profile-service.test.ts
  • apps/desktop/src/main/__tests__/runtime-host-ssh-terminal.test.ts
  • apps/desktop/src/main/runtime-host-boot.ts
  • apps/desktop/src/main/runtime-host-onboarding.ts
  • apps/desktop/src/main/runtime-host-pairing-journal.ts
  • apps/desktop/src/main/runtime-host-profile-service.ts
  • apps/desktop/src/main/runtime-host-ssh-terminal.ts
  • apps/desktop/src/preload/bridge-contract.d.ts
  • apps/desktop/src/renderer/settings/runtime-host-ssh-terminal-dialog.tsx
  • packages/cli/src/__tests__/runtime-host-setup.test.ts
  • packages/cli/src/runtime-host-managed-deployment.ts
  • packages/runtime-host/src/client/index.ts

Included review availability: Your plan provides up to 3 included reviews per hour; 2 remain after this review.

Comment thread apps/desktop/src/main/runtime-host-pairing-journal.ts
Comment thread apps/desktop/src/main/runtime-host-pairing-journal.ts

@Astro-Han Astro-Han 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.

Reviewed exact head 06736029a2e6e78cb51153c39768423efec8259c.

The earlier development-version and crash-window pairing findings are fixed: the generated and recognized dev-<digest> grammar is aligned, and Desktop now persists a bounded atomic pairing journal before profile mutation, replays enable plus idempotent finalization after restart, and restores the previous credential after a permanent failure. The requested screenshot and all ten Codex trailers are present, and all prior threads are resolved.

One current-main integration blocker remains inline. Please rebase, advance the compatibility epoch, update fixtures, and rerun the still-pending current-base checks. Independent human review remains required because this affects UI, a public protocol, credential security, and release behavior.

AI-assisted review disclosure: Codex reviewed the exact-head delta, prior findings and remediation, recovery tests, current-main protocol integration, live threads and CI, screenshot, and provenance metadata. No external model was used. Astro-Han authorized this review campaign.

中文说明

此前 dev 版本识别和崩溃窗口 pairing recovery 两个 P2 均已修复:版本语法统一,Desktop 会在 profile mutation 前原子持久化恢复 journal,并在重启后完成幂等 finalize 或永久失败回滚。截图、十个 Codex trailer 和旧线程都已核实。当前只剩 inline 所述的 main compatibility epoch 冲突;请 rebase 后升 epoch、更新 fixture 并重跑 CI。

Comment thread packages/runtime-host/src/protocol/index.ts Outdated
@M4n5ter
M4n5ter force-pushed the feat/desktop-managed-remote-onboarding branch 2 times, most recently from 26ddf61 to ea3b69c Compare August 19, 2026 13:55
@M4n5ter
M4n5ter requested a review from Astro-Han August 19, 2026 15:18
@M4n5ter

M4n5ter commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

Release note on the CLI version used by Desktop onboarding:

When this work began, the repository had not yet moved into the Apache organization, and the working assumption was that the next CLI version could be published immediately after merge. That assumption no longer holds after the migration: merging this PR does not itself make a new npm package available.

The implementation has therefore been adjusted so a packaged Desktop build does not hard-code or implicitly resolve an unpublished or floating CLI version. The release workflow now requires an exact already-published maka-agent@<version> input, verifies that exact version against npm, and records it in the packaged application metadata. Development builds remain independently testable through the explicit local archive override.

0.1.0-beta.2 is still the intended next CLI package version in this branch, but a Desktop release must select a version that has actually completed the Apache release and publication process. This keeps managed onboarding reproducible and avoids coupling a Desktop artifact to latest, next, or an npm version that does not yet exist.

@M4n5ter

M4n5ter commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

Release-policy clarification: the Runtime Host setup package input is an operator-controlled release decision. We trust an authorized Desktop publisher to select an exact, already-published Maka CLI version that is compatible with the Desktop build being released. It is not an untrusted runtime input, and the workflow intentionally does not try to infer semantic CLI compatibility from source versions or add a second capability/version authority. The existing checks keep the selected package immutable and verify that the exact version exists on npm; compatibility remains part of the publisher review and release checklist.

@M4n5ter
M4n5ter requested a review from Astro-Han August 20, 2026 13:00
@M4n5ter
M4n5ter force-pushed the feat/desktop-managed-remote-onboarding branch from 33bfae8 to 6cc6cd1 Compare August 20, 2026 14:29
@M4n5ter

M4n5ter commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

@Astro-Han, thank you for the careful reviews. Could you please re-review the current exact head 6cc6cd1a9?

Since the previous round, the branch has been rebased onto the latest main; the independent wire change on main remains epoch 28 and staged credential pairing now advances the compatibility epoch to 29. The SSH setup lifecycle also retains ownership of a completed-but-still-running process until exit, so Desktop shutdown can terminate it cleanly. The release CLI selection remains an explicit trusted-publisher decision, as clarified above.

The rebased branch passes npm run build:test, the full workspace typecheck, targeted Biome/diff checks, and 80 relevant tests.

Automated by Codex on behalf of M4n5ter.

@Astro-Han Astro-Han 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.

Re-reviewed at 6cc6cd1a9. I ran five scoped sub-reviews — problem definition, simplification, security, test quality, and a delta audit of my own 31 earlier findings — and re-derived everything published here myself.

First, the part that deserves to be said plainly: the response to review has been excellent. Of my 31 findings, 20 are fixed at this head, and several are fixed at the cause rather than the symptom — the shared-secret writer got a real fsync fence, the "profile becomes enabled" logic collapsed to one owner, the journal became a per-profile map so one stuck intent no longer blocks every other Host, and the npm branch is now pinned to a mktemp -d prefix. Two more you rebutted correctly and I withdraw. That is a high rate on hard findings.

And yet I am not going to file a thirty-second finding, because I do not think the remaining problem is a list of defects. Fifteen commits, of which one is the feature and fourteen are fix/harden/bound/reconcile/preserve/retain, almost all landing on pairing and recovery. The individual answers are mostly right and the count still climbs. Two facts show the shape directly rather than by size argument: babf67971 added active = undefined; to completePresentation() to release the terminal slot, and 6cc6cd1a9, the very next commit, deletes that exact line because releasing early meant a completed setup process was no longer owned by the terminal lifecycle; and 2d3887e26 routed finalization through #mutateTarget and added waits for in-flight pairing finalization before closing its target, after which 66baffa38 broke the wait that serialization created, b67803d35 deleted the test, and babf67971 added the timeout that actually bounds it — the asserted guarantee no longer exists at this head. Across those thirteen commits runtime-host-ssh-terminal.ts was touched eight times, runtime-host-profile-service.ts six, access-authority.ts five; access-authority.ts grew 71% and profile-service.ts 44%, and no commit ever removed a state.

The cause is a write order, and it is a choice this PR makes rather than one the Host imposes. addAndEnableVerified calls rebindIfCurrent to overwrite the existing profile's credential slot before the Host commits, so the pairing needs a rollback; the rollback needs the previous credential to be durable; that durability is the journal, and the journal is a second persistent coordinator for a transaction the Host already owns with pending/expiresAt/a 15-minute expiry. Every one of the last five commits is a boundary patch between those two coordinators, and their bounds are still unaligned — 30 s on the Desktop side against 15 minutes on the Host's, with nothing recording the coupling. The destructive step is not required: credential slots are keyed by profile id (host-profile.ts:626), so a new profile id never touches the old slot; the catalog already refuses target changes with "A Runtime Host profile target cannot be changed; create a new profile id" (host-profile.ts:380), which makes rebindIfCurrent a new exception to an existing rule rather than an extension of it; the Host's AccessCredentialFile.credentials is a list, so credentials coexist by design; and authenticate() grants a pending credential the same operationGrants as an active one while prepare revokes nothing, so both keys work throughout the window. That last point matters most: at rollback time the old credential was never invalidated and the new one is still usable, so the rollback is not recovering access, only bookkeeping. Create the profile, verify it, swap, and delete the old one on success — the journal, its recovery path, its two snapshot flags, its UI entry point, and the failure mode at profile-service.ts:408 ("pairing failed and its previous profile could not be restored", a state that exists only because rollback exists) all disappear together. I estimate around 500 lines, and the two open P2s below at :530 and :562 are both inside code that would no longer exist.

Two architecture-level points for a maintainer rather than for this thread. This PR's delivered artefact is a protocol change — two new wire operations, a new credential state with an expiry, a new error code, a new entry in REMOTE_OWNER_OPERATION_GRANTS next to a comment saying such grants must be updated deliberately, and an epoch bump that hard-rejects mismatched peers — while #3233 describes UI over an unchanged contract and does not mention any of it. Separately, release-desktop.yml gains a required: true input that binds every Desktop release, including releases unrelated to remote hosts, to the CLI release train; that is a release-process decision, it is not in #3233, and reverting this PR would revert it, which by the one-intent rule makes it a second intent.

Three notes that do not warrant their own threads. The rebind fix at b67803d35 removed the named-profile-with-remedy message that existed at 3f403af23:496, so the enabled-conflict case now fails with the opaque Runtime Host ${rootId} is already enabled — a State Root id, no profile name, no remedy — and it fires only after the full SSH setup has run; that is a regression caused by a fix. The mktemp -d cleanup added at babf67971 uses trap ... EXIT, which does not run on the SIGHUP the remote foreground process group receives when Desktop terminates ssh -tt, so cancelled setups leave a directory on every target machine; add HUP INT TERM. And requireSetupDestination accepts internal whitespace and control characters that the existing requireSshDestination rejects, so the same value has two validators with the weaker one at the front door — reuse the existing one.

I also want to correct something I published on this PR earlier: I argued that identical = N epoch text merges cleanly and lets two PRs silently share an epoch. That is wrong. main moved to 28 today and all six epoch-28 claimants went CONFLICTING immediately, because the differing comment block below the constant conflicts. Git blocks the second merge; the residual risk is only that a human resolving that conflict keeps both comment blocks under one number.

Carrying three P1s, so this is a COMMENT.

AI disclosure: reviewed with Claude Code (Opus 5), using five scoped sub-reviews as leads. Every finding published here I verified myself at 6cc6cd1a9: I reproduced the renderer credential leak by executing a verbatim port of createSetupOutputFilter; I read the setup-frame schema, the onboarding destructuring and the profile type to confirm credentialId is received and dropped; I read packages/runtime-host/src/protocol/index.ts:75 at all three PR heads for the epoch collision, and confirmed the conflict behaviour against today's main; I traced the resolvePairingRecovery call chain to writeRuntimeHostPreferences; I grepped both test files for the two uncovered error types; and I read the systemd unit and both systemctl call sites. Sub-review findings I could not confirm are not included, and I downgraded or reframed several that did not survive checking. Per AGENTS.md this is not independent human review.

Comment thread apps/desktop/src/main/runtime-host-ssh-terminal.ts
Comment thread apps/desktop/src/main/runtime-host-onboarding.ts
Comment thread packages/runtime-host/src/protocol/index.ts Outdated
Comment thread apps/desktop/src/main/runtime-host-profile-service.ts
Comment thread apps/desktop/src/main/runtime-host-profile-service.ts
Comment thread apps/desktop/src/main/runtime-host-desktop-manager.ts Outdated
Comment thread packages/cli/src/runtime-host-systemd-service.ts
Comment thread packages/runtime-host/src/__tests__/protocol.test.ts Outdated
@Astro-Han

Copy link
Copy Markdown
Contributor

Following up on my review with the concrete target design, because "the write order is wrong" is not something you can act on and I do not want to leave you patching the eight threads instead.

Everything below uses APIs that already exist at 6cc6cd1a9. I verified each one rather than sketching.

The one fact the design rests on

assertRootIsNotEnabled (runtime-host-profile-service.ts:654) rejects a second enabled profile for a rootId. It does not reject a second profile. So a new profile can be created alongside the old one and stay disabled until it has proven itself — no invariant needs to change, and no new rule needs to be introduced.

Proposal 1 — create, verify, swap, delete. The journal disappears.

addAndEnableVerified becomes a variant of the addAndEnable you already have, with a supersede step:

addAndEnableVerified(value) {
  requireSaveInput(value);
  return mutateProfiles(async () => {
    const document = await catalog.read();
    const superseded = document.profiles.find(
      (p) => p.kind === 'remote' && p.rootId === value.profile.rootId,
    );
    const supersededWasEnabled =
      superseded !== undefined &&
      preferences.enabledRemoteProfileIds.includes(superseded.id);

    // 1. Create under the fresh id onboarding already minted. Nothing is overwritten.
    const created = await catalog.create(value.profile, value.credential);
    const profile = created.profiles.find((c) => c.id === value.profile.id);
    if (!profile) throw new Error('Runtime Host profile creation did not persist');
    const target = { profile, credential: value.credential } as const;

    try {
      // 2. Free the at-most-one-enabled invariant.
      if (superseded && supersededWasEnabled) await disableProfile(superseded.id);
      // 3. Prove the new profile. assertRootIsNotEnabled now passes.
      const error = await enable(profile.id);
      if (error) throw error;
      // 4. Commit. Only now is the old profile redundant.
      if (superseded) {
        if (preferences.defaultProfileId === superseded.id) await setDefaultProfile(profile.id);
        await catalog.remove(superseded.id);
      }
      return { profileId: profile.id };
    } catch (failure) {
      await rollbackCreatedProfile(catalog, target, failure);          // already exists, :639
      if (superseded && supersededWasEnabled) {
        await enable(superseded.id).catch(() => {});                    // restore a flag, not a secret
      }
      throw failure;
    }
  });
}

Why this needs no journal: nothing durable is ever overwritten. catalog.create writes a new profile under a new id, and credential slots are keyed runtime-host-profile:${profile.id}:${binding} (host-profile.ts:626), so the old credential is untouched at every instant. The only compensating action is removing a profile this call created, which is rollbackCreatedProfile — the existing helper addAndEnable already uses. There is no previous credential to remember, so there is nothing to make durable, so there is no second coordinator.

What a crash leaves behind, at every point in the sequence: the old profile intact, plus possibly a new profile that is disabled or unavailable. Both are ordinary rows in Settings. Re-running Add computer is idempotent — it mints another id and supersedes again. That is the entire behavioural delta versus today, and I want to state the cost honestly rather than pretend it is free: the current design self-heals a crashed pairing, this one leaves the user one visible row to delete. My argument is that ~500 lines, a second persistent coordinator, and fourteen hardening commits is a steep price for removing one click, but that is your product call to make explicitly, not mine to assume.

Deleting, once this lands:

  • runtime-host-pairing-journal.ts entirely (196 lines)
  • beginPairingIntent, finishPairingIntent, rollbackPairingIntent, recoverPairingIntent, recoverPendingPairings, clearPairingIntentBestEffort, persistPairingIntents — they only call each other
  • resolvePairingRecovery and its IPC channel, preload.ts binding, bridge-contract.d.ts entry, the renderer button, and the four copy strings in both locales
  • the pairingRecoveryBlocked / pairingRecoveryPending snapshot flags
  • rebindIfCurrent and its interface method — its only two call sites are inside the pairing path, and removing it restores host-profile.ts:380 ("A Runtime Host profile target cannot be changed; create a new profile id") as the single owner of that rule instead of leaving it with an exception
  • the pairingReadFailure gate in mutateProfiles, and interactiveStartup in startEnabledProfiles

Both open P2s from my review — the untested interrupt branch at :530 and the resolvePairingRecovery preferences-write escape at :562 — are inside code this deletes. They do not need separate fixes.

Proposal 2 — let the Host finalize itself. access.credential.finalize disappears.

finalize means "the new credential proved usable, so retire the old one." Desktop proves it by connecting and then sending a command. The Host can observe the same proof directly: authority.credentialId is already carried into ConnectionContext (connection-session.ts:206-207, operation-dispatcher.ts:20) by this PR. Promote pending to active on that credential's first successful operation dispatch and the client-side handshake is unnecessary.

The proof is strictly stronger, not weaker — it demonstrates the credential can do work, rather than that it can send one specific command. And it costs nothing in security: authenticate() (access-authority.ts:103-120) already grants a pending credential exactly the same operationGrants as an active one, bounded only by expiry, so an attacker holding a pending credential gains nothing from automatic promotion that they did not already have.

Deleting: the access.credential.finalize operation spec, its codecs, its host-kernel.ts handler, RuntimeHostAccessAuthority.finalize, the operationGrants.includes('access.credential.finalize') check in prepare, the entry in REMOTE_OWNER_OPERATION_GRANTS, and on the Desktop side finalizePairing, #finalizePairing, pairingFinalizeRetry, pairingFinalizationTimeoutMs, #pairingFinalizationShutdown and RuntimeHostPairingFinalizationInterruptedError. My P2 about the untested dispatch === 'dispatched' gate is inside that block, and the two unaligned deadlines — 30 s on Desktop against the Host's 15 minutes — collapse to the Host's one.

It also halves the epoch justification: with finalize gone, this PR adds one wire operation instead of two.

What this leaves

Taken together the client-side pairing transaction has no intermediate state at all. An unfinished pairing is just "a profile that has not connected yet," which the existing readiness machinery already models and displays. That is what makes the fourteen hardening commits unnecessary rather than merely fewer: they are bounding, fencing, reconciling and preserving a state space that stops existing.

I am not asking for a rewrite of the SSH, setup-frame, tunnel, or onboarding-orchestration work — that part reuses the existing seams correctly and I said so in my review. This is confined to how the verified result is committed.

If you would rather keep the journal, the thing I would want on the record is the answer to "why is self-healing a crashed pairing worth a second persistent coordinator," because that is the trade being made, and right now it is implicit.

AI disclosure: written with Claude Code (Opus 5). Every API used above I read at 6cc6cd1a9 before proposing it — catalog.create/remove/removeIfCurrent, rollbackCreatedProfile:639, the existing addAndEnable shape, assertRootIsNotEnabled:654 permitting multiple profiles per rootId, the profile-id-keyed credential slot at host-profile.ts:626, and the credentialId plumbing into ConnectionContext. I have not implemented or executed this design; it is a proposal, not a tested patch. Per AGENTS.md this is not independent human review.

@M4n5ter

M4n5ter commented Aug 21, 2026

Copy link
Copy Markdown
Member Author

@Astro-Han

English

Thank you for the detailed re-review and, especially, for proposing a concrete alternative instead of only identifying complexity. The concern is valid: the pairing path has accumulated enough recovery logic that its state model deserves explicit re-evaluation.

After tracing the complete setup and failure ordering, I do not think the two proposed architectural changes preserve the required guarantees.

Why we are keeping the Desktop pairing journal

The proposed “create, verify, swap, delete” sequence does not remove the local transaction:

  • An enabled Profile that is also the default cannot be disabled before the replacement becomes the default.
  • Crashes after changing enablement or the default still leave durable intermediate state.
  • A crash may leave an incomplete Profile for the user to diagnose and delete, contrary to this issue’s success criterion that incomplete setup must not be persisted.
  • Profile rows would effectively become an implicit transaction log, but without an explicit recovery state describing which Profile should win.

The Host and Desktop are not duplicating the same authority. The Host owns the credential transition from pending to active and the revocation of the previous credential. Desktop owns the durable Profile, credential binding, enablement and default selection. The journal records only the Desktop participant’s unfinished local transition so those two authorities can be reconciled after interruption.

The proposed sequence also does not work for the common case where the superseded Profile is the current default: it cannot be disabled first, while the replacement cannot become the default until it has been enabled.

For those reasons, we will retain the explicit journal and staged finalization rather than move the same intermediate state into ordinary Profile rows.

Why finalization must remain explicit

The remote setup command verifies the pending credential before emitting the complete frame. That verification connects to the Host and successfully calls host.status.

If the Host promoted a pending credential on its first successful operation, this verification would finalize the credential and revoke the previous one before Desktop had received or persisted the new credential. An SSH interruption between verification and delivery would then leave Desktop with the revoked old credential and no saved replacement—the exact handoff failure staged finalization is intended to prevent.

Therefore access.credential.finalize must remain the explicit commit point after Desktop has safely bound the delivered credential.

Findings we agree should be addressed

We will address the confirmed issues with bounded changes:

  • Rebase onto the latest main and assign a new compatibility epoch; main now owns epoch 29.
  • Suppress the remainder of an oversized reserved setup-frame line. A conforming CLI cannot exceed this parser bound, so I do not consider it P1, but the credential-filter invariant should still be complete.
  • Prevent pairing recovery from writing fallback preferences after a preferences read failure.
  • Reset a systemd unit’s failed/start-limit state before the install path restarts it.
  • Clean temporary setup directories on HUP, INT and TERM.
  • Consolidate SSH destination validation instead of retaining a weaker setup-specific validator.
  • Restore an actionable duplicate-Host error containing the conflicting Profile name.
  • Remove the one-way compatibility-epoch ratchet test. Existing tests already exercise a Host one epoch behind the Client, so another duplicate handshake test is unnecessary.
  • Add one focused test for preserving the journal when finalization is interrupted.

The missing dispatch coverage is also a valid observation, but the proposed expected behavior should change. Finalization is idempotent for the current credential, so a connection loss is safely recoverable for both dispatched and not_dispatched; the latter is known not to have executed and should not unnecessarily fail onboarding. We will test and preserve that semantic directly.

Credential ID

I do not plan to add an otherwise unused credentialId field to the generic Profile schema in this PR. Credential inventory, revocation UI and remote uninstall are explicitly outside this issue’s scope, and retaining one local identifier alone would not solve credentials left by another Desktop or a reset user-data directory.

The complete solution should be a Host-authoritative credential inventory and revocation flow, with Profile removal consuming that capability when remote management is implemented. Adding a field without that consumer would expand the persisted contract without delivering the claimed cleanup behavior.

Scope observations

The development-package path is separable in theory, but it is what permits real end-to-end onboarding verification before a compatible CLI can be published under the ASF release process. Splitting it now would increase coordination and review cost without changing the production architecture.

Likewise, requiring the Desktop publisher to select an exact compatible CLI package is intentional: the release process trusts the publisher to choose the package included with that Desktop release.

This keeps the explicit cross-process commit boundary while avoiding additional defensive machinery. The remaining work should close demonstrated gaps and remove ineffective tests, not add another recovery model.

简体中文

感谢这次细致的重新审查,尤其是没有只指出复杂度问题,而是给出了具体替代方案。这个担忧是合理的:pairing 路径已经积累了足够多的恢复逻辑,确实应该重新明确审视它的状态模型。

在完整追踪 setup 顺序和各个失败窗口后,我认为两项架构替代方案无法保留当前所需的保证。

为什么保留 Desktop pairing journal

建议的“create、verify、swap、delete”流程并没有消除本地事务:

  • 已启用并且作为默认项的旧 Profile,不能在替代项成为默认项之前被禁用。
  • 修改 enablement 或默认项后发生崩溃,仍然会留下持久化的中间状态。
  • 崩溃可能留下需要用户识别和删除的不完整 Profile,这与本 issue“不持久化未完成 setup”的完成标准冲突。
  • 普通 Profile 行实际上会变成隐式事务日志,但没有明确的恢复状态说明最终应保留哪个 Profile。

Host 与 Desktop 也并非在维护同一份 authority。Host 负责 credential 从 pending 到 active 的迁移以及撤销旧 credential;Desktop 负责持久化 Profile、credential binding、enablement 和默认选择。Journal 只记录 Desktop 这一参与方尚未完成的本地迁移,以便中断后重新协调这两个 authority。

此外,该建议在旧 Profile 同时是当前默认 Host 这一常见场景下无法执行:旧 Profile 不能先被禁用,而新 Profile 在启用前又不能成为默认项。

因此,我们会保留显式 journal 与 staged finalization,而不是把相同的中间状态转移到普通 Profile 行中。

为什么 finalization 必须保持显式

远程 setup 命令会在输出 complete frame 之前验证 pending credential。该验证会连接 Host,并成功调用 host.status

如果 Host 在 pending credential 的首次成功 operation 上自动完成 finalization,那么这次验证就会在 Desktop 收到并持久化新 credential 之前撤销旧 credential。若 SSH 在验证完成后、credential 交付前中断,Desktop 将只剩已失效的旧 credential,并且没有保存新的替代 credential——这正是 staged finalization 需要避免的交付失败窗口。

因此,access.credential.finalize 必须继续作为 Desktop 安全绑定 credential 之后的显式提交点。

我们确认需要处理的 findings

我们会通过有边界的修改处理确认成立的问题:

  • Rebase 最新 main 并分配新的 compatibility epoch;当前 main 已经占用 epoch 29。
  • oversized reserved setup frame 触发后,丢弃该行剩余内容。合规 CLI 不可能超过 parser bound,因此我不认为它是 P1,但 credential filter 的不泄露约束仍应保持完整。
  • preferences 读取失败时,禁止 pairing recovery 把 fallback preferences 写回磁盘。
  • 安装路径重启 systemd unit 前,清除 failed/start-limit 状态。
  • HUPINTTERM 时清理临时 setup 目录。
  • 统一 SSH destination validation,不再保留更弱的 setup 专用 validator。
  • 恢复包含冲突 Profile 名称和解决方式的可操作错误信息。
  • 删除单向 compatibility epoch ratchet test。现有测试已经覆盖 Client 比 Host 高一个 epoch 的行为,不需要再增加重复 handshake 测试。
  • 增加一个聚焦于 finalization 中断时保留 journal 的测试。

缺少 dispatch 覆盖这一观察也成立,但建议中的预期行为需要调整。对当前 credential 而言,finalization 是幂等的,因此 dispatchednot_dispatched 两种 connection loss 都可以安全恢复;后者已明确没有执行,不应无谓地让 onboarding 失败。我们会直接测试并保留这一语义。

Credential ID

我不计划在本 PR 中向通用 Profile schema 添加一个当前没有消费者的 credentialId 字段。Credential inventory、revocation UI 和远程卸载已经明确不属于本 issue 的范围,而且只保留一个本地 identifier,也无法处理其他 Desktop 创建的 credential 或 user-data 目录重置后的 credential。

完整方案应由 Host 提供权威的 credential inventory 与 revocation 流程;将来实现远程管理时,Profile 删除操作再消费这项能力。仅增加字段而不提供对应消费者,会扩大持久化契约,却不能真正实现评论中描述的清理行为。

关于范围的观察

Development package 路径在概念上可以拆分,但在 ASF release 流程尚未发布兼容 CLI 时,它是进行真实端到端 onboarding 验证的必要能力。现在拆分只会增加协调与审查成本,不会改变生产架构。

同样,要求 Desktop 发布者选择一个精确且兼容的 CLI package 是有意的设计:发布流程信任发布者为该 Desktop release 选择正确的 package。

这一裁决保留了必要的跨进程显式提交边界,同时避免继续增加防御性机制。剩余工作应聚焦于关闭已有证据支持的缺口,并删除无效测试,而不是再引入另一套恢复模型。

AI disclosure: OpenAI Codex posted this maintainer-reviewed comment under M4n5ter's direction.

M4n5ter added 16 commits August 21, 2026 09:51
Let Desktop install, pair, verify, and connect a Linux Runtime Host from one SSH destination while retaining manual profiles for advanced endpoints.

Keep delivered credentials in the main process and continue successful setup directly to remote Project selection. Unpackaged builds may exercise the same service contract with a content-addressed local CLI archive instead of introducing a separate development Host mode.

Generated-by: Codex
Keep the current credential valid until the newly persisted Desktop profile has established its own connection, then let that authenticated connection finalize the handoff. Re-pairing the same Host now atomically rebinds its transport and rolls back on connection failure.

Pin packaged onboarding to an exact CLI package and give content-addressed development archives a safe dev-to-dev replacement path.

Generated-by: Codex
Escalate cancelled or timed-out setup processes from graceful to forced termination and stop waiting after a bounded deadline. Reuse the same lifecycle for setup, development package upload, and terminal shutdown.

Generated-by: Codex
Keep one expiring pending credential per principal while preserving immediate replacement for standalone CLI setup. Commit only the pending candidate, make retries harmless, and align SSH cancellation with the completion boundary.

Generated-by: Codex
Reuse one per-principal staging path for development package uploads so a failed SSH handoff cannot accumulate orphaned archives. Successful setup still removes the staged package normally.

Generated-by: Codex
Replay idempotent credential finalization after uncertain commits or connection replacement, while restoring the prior profile after conclusive failures. Share one exact setup package validator so valid build metadata is accepted consistently.

Generated-by: Codex
Derive the Desktop setup package from the CLI release version so onboarding cannot target a stale command surface. Bound credential-authority shutdown and service replacement behavior, while keeping development archive setup portable and self-cleaning.

Generated-by: Codex
Run the prepare-stage CLI fixture against the version declared by the repository manifest so release validation remains stable across intentional CLI version bumps.

Generated-by: Codex
Terminate interactive SSH independently from onboarding settlement so shutdown cannot wait behind the process it must stop. Contain post-onboarding catalog refresh failures after the hook records the UI error state.

Generated-by: Codex
Align managed development releases with the generated package version grammar. Preserve published credential authority after uncertain commits, persist pending Desktop pairing transactions across process loss, and dismiss interactive SSH presentation when cancellation begins.

Generated-by: Codex
Serialize credential finalization with target shutdown so an in-flight commit can reconcile before its connection is retired. Make secret-file publication durable before dependent profile state is written, and remove the unused pairing intent UUID.

Generated-by: Codex
Advance the compatibility epoch for staged credential operations and let Desktop shutdown interrupt only pairing reconnect waits. Interrupted finalization retains its durable journal so the next startup can reconcile an unknown outcome without rolling back a potentially committed credential.

Generated-by: Codex
Keep setup metadata and pairing recovery scoped to onboarding so Desktop startup and existing remote Hosts remain available after recoverable failures. Model pairing as one durable intent, centralize profile activation, and preserve explicit repair and shutdown boundaries without duplicating defensive state.

Generated-by: Codex
Keep pairing recovery independent per profile so an offline Host does not block other onboarding. Bound credential finalization and release the interactive terminal once its tunnel is established.

Run remote npm setup from an isolated prefix, and require Desktop releases to select an exact CLI package that is already published.

Generated-by: Codex
Keep a completed SSH setup process under the terminal lifecycle until it actually exits, while releasing tunneled processes only after ownership transfers to the connection resource. This lets application shutdown terminate a remote setup that stalls after sending its completion frame.

Generated-by: Codex
Preserve the staged credential handoff while closing the demonstrated recovery and terminal-output gaps. Unify SSH destination validation, make finalization retries follow its idempotent contract, and let repeated Linux setup recover from systemd start limits.

Generated-by: Codex
@M4n5ter
M4n5ter force-pushed the feat/desktop-managed-remote-onboarding branch from 6cc6cd1 to 25bea91 Compare August 21, 2026 02:23
Refresh the generated surface count after the onboarding UI additions so the repository inventory check matches the current product surface.

Generated-by: Codex

@hqhq1025 hqhq1025 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.

Local verification used Node 22.22.1 and npm 11.19.0. Build, typecheck, lint, format, release checks, the Runtime Host suite (1040 tests), and the CLI suite (337 tests) passed. The Desktop suite reproducibly ends with 997 passes and 9 cancellations because of the timeout finding below.

Codex-assisted review performed under the maintainer-approved review workflow.

Comment thread .github/workflows/release-desktop.yml
() => timeout.abort(new RuntimeHostPairingFinalizationInterruptedError()),
this.pairingFinalizationTimeoutMs,
);
timer.unref();

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.

[P1] Keep this deadline alive until finalization settles. Because the timer is unref'ed, the new defers pairing finalization when reconnect does not complete in time test reaches a pending reconnect with no referenced handles; Node exits the event loop before the 10 ms deadline, cancels that test, and cancels the next eight tests in the file. I reproduced this in both the full Desktop suite and a serial rerun: 997 pass, 9 are cancelledByParent with Promise resolution is still pending. Removing unref() is sufficient here because the timer is already cleared in finally and the operation is explicitly required to settle within the deadline.

'Description=Maka Runtime Host',
'After=network.target',
'StartLimitIntervalSec=60s',
'StartLimitBurst=5',

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.

[P2] Clear this start-limit state on the rollback path as well. A replacement that repeatedly exits can consume all five starts during the 45-second readiness wait; rollback then restores the previous unit but calls restart at line 291 without another reset-failed. The limit belongs to the unit name, so systemd rejects the restored service too and a failed update leaves the previously working Host offline. The install-side reset added for the earlier thread does not cover this path; reset before restarting an active snapshot and model the failed/start-limit state in the rollback test.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(desktop): add managed remote Runtime Host onboarding over SSH

3 participants