mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 16:02:55 +00:00
Gate project host setup runtime capability
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -406,8 +406,10 @@ Needs:
|
||||
- structured errors for unsupported old-server actions
|
||||
- old client / new server behavior that degrades safely
|
||||
|
||||
Current branch status: runtime compatibility exists; project/setup capability
|
||||
gating needs to be layered on.
|
||||
Current branch status: runtime compatibility exists. Runtimes now advertise
|
||||
`project-host-setup.v1`; renderer reads fall back to repo-derived projection
|
||||
when the capability is missing, and remote setup/clone mutations are blocked
|
||||
before contacting older servers. Broader UI/CLI version-skew validation remains.
|
||||
|
||||
### 10. Caches And Local State
|
||||
|
||||
@@ -489,6 +491,8 @@ Already partially landed:
|
||||
- inline `Run on` clone setup for not-yet-set-up local, runtime, and SSH hosts
|
||||
- repo-backed setup method metadata so imported/cloned setup methods survive
|
||||
compatibility projection and persistence sync
|
||||
- project-host setup runtime capability advertisement, read fallback, and
|
||||
remote mutation gating for older runtime servers
|
||||
|
||||
Not complete yet:
|
||||
|
||||
@@ -499,7 +503,7 @@ Not complete yet:
|
||||
- project settings split into global and host-specific ownership
|
||||
- host settings/capability UI aligned with project setup
|
||||
- complete cache/request ownership audit
|
||||
- complete version-skew capability gating
|
||||
- broader UI/CLI version-skew validation beyond the runtime capability guard
|
||||
- full Electron and SSH end-to-end validation
|
||||
|
||||
## Recommended Implementation Shape
|
||||
|
||||
@@ -0,0 +1,548 @@
|
||||
# Project-First Host Model Conversation
|
||||
|
||||
## Purpose
|
||||
|
||||
This document captures the VM / SSH / remote-server discussion so far and turns
|
||||
it into a concrete change inventory.
|
||||
|
||||
The product question was:
|
||||
|
||||
```text
|
||||
Should Orca organize work by machine first, or by project first?
|
||||
```
|
||||
|
||||
The current answer is:
|
||||
|
||||
```text
|
||||
Project -> ProjectHostSetup -> Workspace
|
||||
```
|
||||
|
||||
Hosts still matter. They are where code runs. But they should usually be
|
||||
represented as places where a project is available, not as isolated silos that
|
||||
own the user's projects.
|
||||
|
||||
## TL;DR
|
||||
|
||||
The durable model should be project-first.
|
||||
|
||||
```text
|
||||
Project
|
||||
Host setup
|
||||
Workspace
|
||||
```
|
||||
|
||||
There are **12 major change surfaces** needed to fully fit this model.
|
||||
|
||||
If we count smaller implementation tasks inside those surfaces, it is closer to
|
||||
**35-45 concrete changes**, but the clean planning answer is 12 major areas.
|
||||
|
||||
Some of the work is already partially implemented on this branch. The model is
|
||||
not complete until data model, persistence, creation, setup, settings, sidebar,
|
||||
runtime routing, cache ownership, CLI/API, version skew, SSH behavior, and
|
||||
Electron validation all agree on the same project-first language.
|
||||
|
||||
## Current Model
|
||||
|
||||
The current multi-host direction makes hosts highly visible:
|
||||
|
||||
```text
|
||||
Local Mac
|
||||
Orca
|
||||
feature-a
|
||||
|
||||
openclaw 2
|
||||
Orca
|
||||
fix-ssh-agent-status
|
||||
```
|
||||
|
||||
That is useful for operational awareness. It helps answer:
|
||||
|
||||
- which hosts exist
|
||||
- which hosts are online or disconnected
|
||||
- where workspaces are running
|
||||
- which SSH/remote machine might be causing a problem
|
||||
|
||||
The weakness is that it makes the machine feel like the outermost product
|
||||
concept. Users can end up feeling like they are switching into separate machine
|
||||
silos, even when they are really working on the same project.
|
||||
|
||||
## Desired Model
|
||||
|
||||
Projects/repos remain the outermost durable concept:
|
||||
|
||||
```text
|
||||
Orca
|
||||
Local Mac
|
||||
feature-a
|
||||
openclaw 2
|
||||
fix-ssh-agent-status
|
||||
```
|
||||
|
||||
In plain English:
|
||||
|
||||
```text
|
||||
I am working on Project A.
|
||||
Where should this workspace run?
|
||||
```
|
||||
|
||||
not:
|
||||
|
||||
```text
|
||||
I am inside Machine B.
|
||||
Which copy of Project A is here?
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Project
|
||||
|
||||
A `Project` is the durable repo/project identity the user recognizes.
|
||||
|
||||
Examples:
|
||||
|
||||
- Orca
|
||||
- a Linux-only CUDA repo
|
||||
- a work repo available only on a company host
|
||||
- a personal repo available both locally and on an SSH server
|
||||
|
||||
### Host
|
||||
|
||||
A `Host` is a place where code can run.
|
||||
|
||||
Examples:
|
||||
|
||||
- local Mac
|
||||
- SSH target
|
||||
- remote server
|
||||
- user-managed VM
|
||||
- remote Orca runtime
|
||||
- future Orca-provisioned cloud VM
|
||||
|
||||
For now, "VM" is a loose product term. A VM can be modeled as a host with extra
|
||||
capabilities, provisioning metadata, and eventually billing metadata.
|
||||
|
||||
### ProjectHostSetup
|
||||
|
||||
A `ProjectHostSetup` means:
|
||||
|
||||
```text
|
||||
this project is available on this host at this path with this setup state
|
||||
```
|
||||
|
||||
Host-local facts belong here:
|
||||
|
||||
- checkout path
|
||||
- worktree base path
|
||||
- setup status
|
||||
- clone/import/provision method
|
||||
- setup scripts
|
||||
- host-specific project settings
|
||||
- platform or capability constraints
|
||||
|
||||
### Workspace
|
||||
|
||||
A `Workspace` is a branch/task/worktree running from one project setup on one
|
||||
host.
|
||||
|
||||
It should eventually know:
|
||||
|
||||
- `projectId`
|
||||
- `hostId`
|
||||
- `projectHostSetupId`
|
||||
- host-local worktree path
|
||||
|
||||
## Important Product Decisions
|
||||
|
||||
### Host-First Is Still Useful
|
||||
|
||||
Host-first grouping should not disappear completely. It is a useful operational
|
||||
view/filter for troubleshooting, host health, and remote machine management.
|
||||
|
||||
The important distinction is that host-first should be a view mode or filter,
|
||||
not the only durable organization model.
|
||||
|
||||
### Focused Host Mode Is A Filter
|
||||
|
||||
Focused host mode is not a separate durable model.
|
||||
|
||||
Selecting "Local Mac" or another host as a filter can satisfy the same use case.
|
||||
If there is only one host and no meaningful host choice, the host UI should be
|
||||
reduced or hidden.
|
||||
|
||||
### Configured SSH Target Is Not Project Availability
|
||||
|
||||
A configured SSH target should not automatically appear under every project.
|
||||
|
||||
Recommended sidebar behavior:
|
||||
|
||||
- show a disconnected host when it has relevant project setup history or
|
||||
workspaces
|
||||
- do not show a never-used SSH target in a project sidebar just because it is
|
||||
configured
|
||||
- keep SSH connection management in host/settings surfaces
|
||||
- use clear disconnected labels/actions instead of relying only on a gray dot
|
||||
|
||||
### A Remote-Only Project Is Normal
|
||||
|
||||
Some projects only exist on one machine. That is fine.
|
||||
|
||||
Examples:
|
||||
|
||||
- a Linux-only project
|
||||
- a GPU-heavy project
|
||||
- a work project only available on a work machine
|
||||
- a repo checked out only on an SSH server
|
||||
|
||||
The UI should not imply that every project can or should run everywhere.
|
||||
|
||||
## Workspace Creation Direction
|
||||
|
||||
Creating a workspace should eventually ask:
|
||||
|
||||
1. Which project?
|
||||
2. Which host should run it?
|
||||
3. What branch/task/workspace name?
|
||||
|
||||
If the project is not available on the selected host, Orca should offer:
|
||||
|
||||
- clone project to that host
|
||||
- import an existing folder on that host
|
||||
- select a different host
|
||||
|
||||
The user should not need to understand old `repoId` compatibility records.
|
||||
|
||||
## Project Setup Direction
|
||||
|
||||
"Add project" and "make this project available on another host" are related but
|
||||
different actions.
|
||||
|
||||
Important flows:
|
||||
|
||||
- import a local folder as a new project
|
||||
- import an SSH folder as a new project
|
||||
- set up an existing project on another host
|
||||
- clone an existing project onto a selected host
|
||||
- when adding a new host, optionally initialize one or more projects there
|
||||
- later, provision an Orca cloud VM and materialize selected projects there
|
||||
|
||||
## Settings Direction
|
||||
|
||||
Settings need explicit ownership.
|
||||
|
||||
| Setting type | Owner | Examples |
|
||||
| --- | --- | --- |
|
||||
| Client setting | desktop client | theme, local UI preferences |
|
||||
| Host setting | machine/runtime | SSH connection, display name, health, server version |
|
||||
| Project setting | durable project | project name, provider identity |
|
||||
| Project-host setup setting | project on one host | checkout path, worktree base path, setup script |
|
||||
|
||||
A host dropdown or host table inside project settings is probably sufficient for
|
||||
host-specific project settings, similar to existing Windows/WSL-specific
|
||||
settings patterns.
|
||||
|
||||
## Version Skew Direction
|
||||
|
||||
Remote servers may not match the desktop client version.
|
||||
|
||||
Needed behavior:
|
||||
|
||||
- new client + old server should degrade explicitly
|
||||
- project/setup reads can fall back to compatibility projections where possible
|
||||
- setup/create actions should be blocked before mutation if the server lacks
|
||||
the required capability
|
||||
- old client + new server should keep working through compatibility APIs where
|
||||
possible
|
||||
|
||||
The user-facing reasons should be concrete:
|
||||
|
||||
- host is offline
|
||||
- server version does not support project-host setup
|
||||
- project is not set up on this host
|
||||
- selected host does not support required platform/capability
|
||||
- required runtime/agent is unavailable
|
||||
|
||||
## Reference Comparison
|
||||
|
||||
### Superset
|
||||
|
||||
Superset is the closer reference for Orca's desired data model.
|
||||
|
||||
Its conceptual model is effectively:
|
||||
|
||||
```text
|
||||
Project + Host -> Workspace
|
||||
```
|
||||
|
||||
Specific lessons for Orca:
|
||||
|
||||
- project identity is durable
|
||||
- a host is where the project can be materialized
|
||||
- workspace creation targets both project and host
|
||||
- a project can be set up on multiple hosts
|
||||
- if a project is not available on a host, the UI can block and offer setup
|
||||
- project settings can contain host-specific path/worktree settings
|
||||
|
||||
This maps well to Orca because Orca already has durable repos/projects,
|
||||
worktrees, agents, terminals, source control, and host-aware runtimes.
|
||||
|
||||
### Cmux
|
||||
|
||||
Cmux is more session/workspace-first.
|
||||
|
||||
Its conceptual model is closer to:
|
||||
|
||||
```text
|
||||
Workspace/session -> local or remote execution context
|
||||
```
|
||||
|
||||
Specific lessons for Orca:
|
||||
|
||||
- SSH should feel first class
|
||||
- remote terminals, file views, browser panes, and localhost routing should
|
||||
follow the remote execution context
|
||||
- reconnect and persistence behavior matter
|
||||
- remote execution should not feel bolted on
|
||||
|
||||
Cmux is useful for SSH/session polish. It is less useful as the core durable
|
||||
data model reference because it does not appear to center "this project is
|
||||
available on these hosts" as the main object.
|
||||
|
||||
## What Needs To Change
|
||||
|
||||
There are **12 major change surfaces**.
|
||||
|
||||
### 1. Shared Data Model
|
||||
|
||||
Current `Repo` mixes durable project identity with host-local checkout details.
|
||||
The new model needs first-class project/setup concepts.
|
||||
|
||||
Needed:
|
||||
|
||||
- `Project`
|
||||
- `Host`
|
||||
- `ProjectHostSetup`
|
||||
- explicit workspace ownership by `projectId`, `hostId`, and
|
||||
`projectHostSetupId`
|
||||
- compatibility projection from old repo-shaped records
|
||||
|
||||
### 2. Persistence And Migration
|
||||
|
||||
Existing users need a boring migration.
|
||||
|
||||
Needed:
|
||||
|
||||
- derive one project per reliable durable identity
|
||||
- derive one setup per existing repo checkout
|
||||
- avoid merging same-name folders unless provider/setup identity is reliable
|
||||
- preserve old ids or aliases where compatibility requires it
|
||||
- backfill existing workspaces with project/setup ownership when safe
|
||||
|
||||
### 3. Runtime And Request Ownership
|
||||
|
||||
The UI can be project-first, but execution still happens on a host.
|
||||
|
||||
Needed:
|
||||
|
||||
- route terminals, agents, filesystem, browser, source control, hooks, and
|
||||
automations through the workspace's owning host
|
||||
- avoid using the currently focused host as a hidden global default for
|
||||
workspace-owned operations
|
||||
- scope cancellation and stale-response handling to the host/setup that owns the
|
||||
request
|
||||
|
||||
### 4. Workspace Creation
|
||||
|
||||
Creation must target a project and host, not only a repo id.
|
||||
|
||||
Needed:
|
||||
|
||||
- project picker
|
||||
- run-on host picker
|
||||
- unavailable-host reasons
|
||||
- inline clone/import setup actions
|
||||
- compatibility resolver from `{ projectId, hostId }` to current repo/setup
|
||||
internals while old APIs remain
|
||||
|
||||
### 5. Project Setup Flow
|
||||
|
||||
"Add repo" becomes a family of project/setup flows.
|
||||
|
||||
Needed:
|
||||
|
||||
- import existing folder on local host
|
||||
- import existing folder over SSH
|
||||
- clone project onto selected host
|
||||
- set up an existing project on another host
|
||||
- bulk setup when adding a new host
|
||||
- future cloud provisioning hook
|
||||
|
||||
### 6. Sidebar Row Model
|
||||
|
||||
The sidebar should be built from projects, hosts, setups, and workspaces rather
|
||||
than repo-only grouping.
|
||||
|
||||
Needed:
|
||||
|
||||
- project-first grouping
|
||||
- host labels/subgroups only when useful
|
||||
- host filters and online/offline status retained
|
||||
- clear disconnected-host behavior
|
||||
- drag/reorder rules for projects, host sections, and workspaces
|
||||
|
||||
### 7. Project Settings
|
||||
|
||||
Project settings need a global area plus host-specific setup sections.
|
||||
|
||||
Needed:
|
||||
|
||||
- project-global settings
|
||||
- host-specific paths, worktree paths, setup scripts, and platform constraints
|
||||
- host dropdown/table inside settings
|
||||
- provider-neutral source-control settings
|
||||
|
||||
### 8. Host Settings
|
||||
|
||||
Host settings should describe the machine/runtime, not duplicate every project
|
||||
setting.
|
||||
|
||||
Needed:
|
||||
|
||||
- connection details
|
||||
- display name
|
||||
- health/status
|
||||
- server version and protocol compatibility
|
||||
- platform/capabilities
|
||||
- host-wide defaults and overrides
|
||||
|
||||
### 9. Version And Capability Compatibility
|
||||
|
||||
New clients and old servers will coexist.
|
||||
|
||||
Needed:
|
||||
|
||||
- host capability probing
|
||||
- fallback projection when project/setup APIs are missing
|
||||
- disabled states with specific reasons
|
||||
- structured errors for unsupported old-server actions
|
||||
- old client / new server behavior that degrades safely
|
||||
|
||||
### 10. Caches And Local State
|
||||
|
||||
Some caches are project-global; many are host/setup-local.
|
||||
|
||||
Needed:
|
||||
|
||||
- classify caches as project, host, setup, or workspace scoped
|
||||
- include host/setup ids in cache keys for refs, git status, filesystem state,
|
||||
capabilities, terminals, browser sessions, and remote results
|
||||
- prevent a response from one host from overwriting another host's state for the
|
||||
same project
|
||||
|
||||
### 11. CLI And API
|
||||
|
||||
External commands should speak project-first language.
|
||||
|
||||
Needed:
|
||||
|
||||
- `orca project list`
|
||||
- `orca project setups`
|
||||
- `orca project setup-existing-folder --project <id> --host <id> --path <path>`
|
||||
- `orca worktree create --project <id> --host <id> ...`
|
||||
- `orca worktree create --project-host-setup <id> ...`
|
||||
- compatibility aliases for old repo/worktree commands
|
||||
- structured availability errors
|
||||
|
||||
### 12. Tests And Verification
|
||||
|
||||
This crosses storage, routing, UI, SSH, and compatibility.
|
||||
|
||||
Needed:
|
||||
|
||||
- migration tests
|
||||
- selector/sidebar grouping tests
|
||||
- create-workspace tests
|
||||
- setup-on-host tests
|
||||
- settings ownership tests
|
||||
- SSH end-to-end validation
|
||||
- version mismatch tests
|
||||
- Electron validation for sidebar, creation, and settings
|
||||
|
||||
## Current Branch Status
|
||||
|
||||
Already partially landed:
|
||||
|
||||
- shared `Project` and `ProjectHostSetup` types
|
||||
- compatibility projection from existing `Repo[]`
|
||||
- persisted compatibility fields for projects and project-host setups
|
||||
- renderer hydration and fallback for older runtimes
|
||||
- project-aware sidebar grouping in existing repo grouping paths
|
||||
- default all-host Projects sidebar keeps projects outermost while preserving
|
||||
host-section operational views for explicit host filters
|
||||
- first-pass host context badges for mixed-host project groups
|
||||
- project-host-aware workspace creation target resolver
|
||||
- optional workspace metadata for `projectId`, `hostId`, and
|
||||
`projectHostSetupId`
|
||||
- discovery-time backfill for missing workspace ownership fields
|
||||
- setup-existing-folder API plumbing through local IPC/preload/runtime paths
|
||||
- project settings existing-folder setup form for known local, SSH, and active
|
||||
runtime hosts
|
||||
- CLI commands for listing projects/setups and creating worktrees by
|
||||
project/setup
|
||||
- composer `Run on` selector when a project has multiple ready setups
|
||||
- setup-target `Run on` rows for known hosts where the selected project is not
|
||||
set up yet
|
||||
- inline `Run on` import-existing-folder setup
|
||||
- inline `Run on` clone setup for not-yet-set-up local, runtime, and SSH hosts
|
||||
- repo-backed setup method metadata so imported/cloned setup methods survive
|
||||
compatibility projection and persistence sync
|
||||
- project-host setup runtime capability advertisement, read fallback, and
|
||||
remote mutation gating for older runtime servers
|
||||
|
||||
Not complete yet:
|
||||
|
||||
- independent project-host setup persistence beyond repo-backed compatibility
|
||||
records
|
||||
- bulk setup flows and setup for hosts that are not already known to the client
|
||||
- SSH streamed clone progress parity
|
||||
- full project settings split into global and host-specific ownership
|
||||
- host settings/capability UI aligned with project setup
|
||||
- complete cache/request ownership audit
|
||||
- broader UI/CLI version-skew validation beyond the runtime capability guard
|
||||
- full Electron and SSH end-to-end validation
|
||||
|
||||
## Change Count
|
||||
|
||||
Short answer:
|
||||
|
||||
```text
|
||||
12 major change surfaces
|
||||
```
|
||||
|
||||
More practical engineering answer:
|
||||
|
||||
```text
|
||||
About 35-45 concrete implementation tasks
|
||||
```
|
||||
|
||||
The reason for the difference is that each major surface has several necessary
|
||||
subtasks. For example, "workspace creation" includes resolver changes, UI
|
||||
changes, setup actions, unavailable-host reasons, runtime routing, persistence,
|
||||
tests, and version-skew handling.
|
||||
|
||||
The largest remaining areas are:
|
||||
|
||||
1. independent `ProjectHostSetup` persistence
|
||||
2. project-first creation/setup UI completion
|
||||
3. project and host settings ownership split
|
||||
4. cache/request ownership audit
|
||||
5. broader version-skew validation
|
||||
6. SSH parity and validation
|
||||
7. full Electron validation of sidebar, creation, and settings
|
||||
|
||||
## Final Position
|
||||
|
||||
Keep hosts visible, but do not make them the durable outermost object.
|
||||
|
||||
The long-term model should be project-first because it matches how users think
|
||||
about repo/worktree/task work. Hosts, SSH machines, VMs, remote servers, and
|
||||
future Orca cloud compute become first-class places where a project can run.
|
||||
@@ -445,8 +445,10 @@ Needs:
|
||||
- structured errors for unsupported old-server actions
|
||||
- old client / new server behavior that degrades safely
|
||||
|
||||
Current branch status: runtime compatibility exists; project/setup capability
|
||||
gating needs to be layered on.
|
||||
Current branch status: runtime compatibility exists. Runtimes now advertise
|
||||
`project-host-setup.v1`; renderer reads fall back to repo-derived projection
|
||||
when the capability is missing, and remote setup/clone mutations are blocked
|
||||
before contacting older servers. Broader UI/CLI version-skew validation remains.
|
||||
|
||||
### 10. Caches And Local State
|
||||
|
||||
@@ -528,6 +530,8 @@ Already partially landed:
|
||||
- inline `Run on` clone setup for not-yet-set-up local, runtime, and SSH hosts
|
||||
- repo-backed setup method metadata so imported and cloned setups survive
|
||||
compatibility projection and persistence sync
|
||||
- project-host setup runtime capability advertisement, read fallback, and
|
||||
remote mutation gating for older runtime servers
|
||||
|
||||
Not complete yet:
|
||||
|
||||
@@ -539,7 +543,7 @@ Not complete yet:
|
||||
- full project settings split into global and host-specific ownership
|
||||
- host settings/capability UI aligned with project setup
|
||||
- complete cache/request ownership audit
|
||||
- complete version-skew capability gating
|
||||
- broader UI/CLI version-skew validation beyond the runtime capability guard
|
||||
- full Electron and SSH end-to-end validation
|
||||
|
||||
## Recommended Implementation Shape
|
||||
|
||||
@@ -315,7 +315,10 @@ Needs:
|
||||
- structured errors for unsupported old-server actions
|
||||
- old client / new server behavior that degrades safely
|
||||
|
||||
Status: runtime compatibility exists; project/setup capability gating remains.
|
||||
Status: runtime compatibility exists. Runtimes advertise
|
||||
`project-host-setup.v1`; renderer reads fall back to repo-derived projection
|
||||
when the capability is missing, and remote setup/clone mutations are blocked
|
||||
before contacting older servers. Broader UI/CLI version-skew validation remains.
|
||||
|
||||
### 10. Caches And Local State
|
||||
|
||||
@@ -410,6 +413,8 @@ Already partially landed:
|
||||
- inline clone setup from the composer for not-yet-set-up local, runtime, and
|
||||
SSH hosts
|
||||
- repo-backed setup method metadata for imported/cloned project-host setups
|
||||
- project-host setup runtime capability advertisement, read fallback, and
|
||||
remote mutation gating for older runtime servers
|
||||
|
||||
Still needed:
|
||||
|
||||
@@ -420,7 +425,7 @@ Still needed:
|
||||
- project settings split into global and host-specific ownership
|
||||
- host settings/capability UI aligned with project setup
|
||||
- complete cache/request ownership audit
|
||||
- complete version-skew capability gating
|
||||
- broader UI/CLI version-skew validation beyond the runtime capability guard
|
||||
- full Electron and SSH end-to-end validation
|
||||
|
||||
## Final Product Position
|
||||
|
||||
@@ -762,15 +762,20 @@ Landed so far:
|
||||
- Added repo-backed setup method metadata so imported and cloned setup flows
|
||||
survive persistence/projection sync as `imported-existing-folder` or `cloned`
|
||||
while `Repo` remains the compatibility source of truth.
|
||||
- Added a runtime capability contract for project-host setup. New runtimes
|
||||
advertise `project-host-setup.v1`; new clients fall back to repo-derived
|
||||
project/setup projection for reads when that capability is missing and block
|
||||
remote setup/clone mutations before contacting an older server.
|
||||
- Added tests for local repos, SSH repos, same-provider multi-host grouping,
|
||||
no-identity same-name non-grouping, selector cache behavior, persistence
|
||||
backfill, repo mutation synchronization, renderer hydration, runtime RPC
|
||||
routing, setup method persistence, local/runtime/SSH clone setup composition,
|
||||
remote clone IPC, and GitHub clone URL inference. Sidebar row-builder tests
|
||||
now cover project-first multi-host grouping and same-name repo separation
|
||||
without project identity. Workspace target tests cover local-only fallback,
|
||||
focused-host setup selection, explicit project-plus-host resolution, same-name
|
||||
non-merging, and unavailable setup reasons.
|
||||
routing, project-host setup capability gating, setup method persistence,
|
||||
local/runtime/SSH clone setup composition, remote clone IPC, and GitHub clone
|
||||
URL inference. Sidebar row-builder tests now cover project-first multi-host
|
||||
grouping and same-name repo separation without project identity. Workspace
|
||||
target tests cover local-only fallback, focused-host setup selection, explicit
|
||||
project-plus-host resolution, same-name non-merging, and unavailable setup
|
||||
reasons.
|
||||
|
||||
Important limitation:
|
||||
|
||||
@@ -805,8 +810,9 @@ Remaining end-to-end work:
|
||||
handling for host/setup-local ownership
|
||||
- add project-first CLI/API commands with compatibility aliases for existing
|
||||
repo/worktree commands
|
||||
- validate migration, creation, settings, sidebar, SSH, and version-skew flows
|
||||
end to end
|
||||
- broaden version-skew UI/CLI coverage beyond the current runtime capability
|
||||
guard, and validate migration, creation, settings, sidebar, SSH, and
|
||||
version-skew flows end to end
|
||||
|
||||
## Recommendation
|
||||
|
||||
|
||||
@@ -897,6 +897,7 @@ describe('OrcaRuntimeService', () => {
|
||||
expect(status.capabilities).toContain('terminal.binary-stream.v1')
|
||||
expect(status.capabilities).toContain('workspace-ports.v1')
|
||||
expect(status.capabilities).toContain('mobile.tasks.v1')
|
||||
expect(status.capabilities).toContain('project-host-setup.v1')
|
||||
expect(typeof status.protocolVersion).toBe('number')
|
||||
expect(typeof status.minCompatibleMobileVersion).toBe('number')
|
||||
expect(status.protocolVersion).toBeGreaterThanOrEqual(1)
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { RuntimeRpcResponse } from '../../../shared/runtime-rpc-envelope'
|
||||
import type { RuntimeStatus } from '../../../shared/runtime-types'
|
||||
import {
|
||||
MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION,
|
||||
RUNTIME_CAPABILITIES,
|
||||
RUNTIME_PROTOCOL_VERSION
|
||||
} from '../../../shared/protocol-version'
|
||||
|
||||
@@ -23,7 +24,8 @@ export function createCompatibleRuntimeStatusResponse(
|
||||
liveTabCount: 0,
|
||||
liveLeafCount: 0,
|
||||
runtimeProtocolVersion: RUNTIME_PROTOCOL_VERSION,
|
||||
minCompatibleRuntimeClientVersion: MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION
|
||||
minCompatibleRuntimeClientVersion: MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION,
|
||||
capabilities: [...RUNTIME_CAPABILITIES]
|
||||
},
|
||||
_meta: { runtimeId }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
callRuntimeRpc,
|
||||
assertRuntimeEnvironmentCapability,
|
||||
clearRuntimeCompatibilityCacheForTests,
|
||||
getActiveRuntimeTarget,
|
||||
RuntimeRpcCallError,
|
||||
@@ -135,6 +136,52 @@ describe('runtime RPC client routing', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('checks advertised runtime capabilities after protocol compatibility', async () => {
|
||||
runtimeEnvironmentCall.mockResolvedValue({
|
||||
id: 'status',
|
||||
ok: true,
|
||||
result: {
|
||||
runtimeId: 'remote-runtime',
|
||||
graphStatus: 'ready',
|
||||
runtimeProtocolVersion: RUNTIME_PROTOCOL_VERSION,
|
||||
minCompatibleRuntimeClientVersion: MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION,
|
||||
capabilities: ['project-host-setup.v1']
|
||||
},
|
||||
_meta: { runtimeId: 'remote-runtime' }
|
||||
})
|
||||
|
||||
await expect(
|
||||
assertRuntimeEnvironmentCapability(
|
||||
'env-1',
|
||||
'project-host-setup.v1',
|
||||
'Project setup is unavailable.'
|
||||
)
|
||||
).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
it('rejects missing advertised runtime capabilities with the caller message', async () => {
|
||||
runtimeEnvironmentCall.mockResolvedValue({
|
||||
id: 'status',
|
||||
ok: true,
|
||||
result: {
|
||||
runtimeId: 'remote-runtime',
|
||||
graphStatus: 'ready',
|
||||
runtimeProtocolVersion: RUNTIME_PROTOCOL_VERSION,
|
||||
minCompatibleRuntimeClientVersion: MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION,
|
||||
capabilities: []
|
||||
},
|
||||
_meta: { runtimeId: 'remote-runtime' }
|
||||
})
|
||||
|
||||
await expect(
|
||||
assertRuntimeEnvironmentCapability(
|
||||
'env-1',
|
||||
'project-host-setup.v1',
|
||||
'Project setup is unavailable.'
|
||||
)
|
||||
).rejects.toThrow('Project setup is unavailable.')
|
||||
})
|
||||
|
||||
it('marks remote UI-owned runtime calls so feature interaction tracking can ignore them', async () => {
|
||||
runtimeEnvironmentCall.mockImplementation(({ method }: { method: string }) => {
|
||||
const result =
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { GlobalSettings } from '../../../shared/types'
|
||||
import type { RuntimeRpcFailure, RuntimeRpcResponse } from '../../../shared/runtime-rpc-envelope'
|
||||
import type { RuntimeStatus } from '../../../shared/runtime-types'
|
||||
import type { RuntimeCapability } from '../../../shared/protocol-version'
|
||||
import { withBrowserPaneUiRuntimeRpcSource } from '../../../shared/runtime-rpc-feature-interaction-source'
|
||||
import { assertRuntimeStatusCompatible } from './runtime-protocol-compat'
|
||||
|
||||
@@ -141,6 +142,35 @@ export function markRuntimeEnvironmentCompatible(environmentId: string): void {
|
||||
rememberRuntimeEnvironmentCompatibility(trimmed, Promise.resolve())
|
||||
}
|
||||
|
||||
export async function getRuntimeEnvironmentStatus(
|
||||
environmentId: string,
|
||||
timeoutMs?: number
|
||||
): Promise<RuntimeStatus> {
|
||||
const response = await window.api.runtimeEnvironments.call({
|
||||
selector: environmentId,
|
||||
method: 'status.get',
|
||||
timeoutMs
|
||||
})
|
||||
const status = unwrapRuntimeRpcResult<RuntimeStatus>(
|
||||
response as RuntimeRpcResponse<RuntimeStatus>
|
||||
)
|
||||
assertRuntimeStatusCompatible(status)
|
||||
markRuntimeEnvironmentCompatible(environmentId)
|
||||
return status
|
||||
}
|
||||
|
||||
export async function assertRuntimeEnvironmentCapability(
|
||||
environmentId: string,
|
||||
capability: RuntimeCapability,
|
||||
message: string,
|
||||
timeoutMs?: number
|
||||
): Promise<void> {
|
||||
const status = await getRuntimeEnvironmentStatus(environmentId, timeoutMs)
|
||||
if (!status.capabilities?.includes(capability)) {
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
export function clearRuntimeCompatibilityCacheForTests(): void {
|
||||
clearRuntimeCompatibilityCache()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { Repo } from '../../../../shared/types'
|
||||
import { clearRuntimeCompatibilityCacheForTests } from '../../runtime/runtime-rpc-client'
|
||||
import type { RuntimeEnvironmentCallRequest } from '../../runtime/runtime-compatibility-test-fixture'
|
||||
import { createTestStore } from './store-test-helpers'
|
||||
|
||||
const remoteRepo: Repo = {
|
||||
id: 'remote-repo',
|
||||
path: '/remote',
|
||||
displayName: 'Remote',
|
||||
badgeColor: '#111',
|
||||
addedAt: 2
|
||||
}
|
||||
|
||||
const reposList = vi.fn()
|
||||
const reposClone = vi.fn()
|
||||
const reposCloneRemote = vi.fn()
|
||||
const runtimeEnvironmentCall = vi.fn()
|
||||
const runtimeEnvironmentTransportCall = vi.fn()
|
||||
|
||||
function runtimeStatusWithoutProjectHostSetup() {
|
||||
return {
|
||||
id: 'status',
|
||||
ok: true,
|
||||
result: {
|
||||
runtimeId: 'runtime-remote',
|
||||
rendererGraphEpoch: 0,
|
||||
graphStatus: 'ready',
|
||||
authoritativeWindowId: null,
|
||||
liveTabCount: 0,
|
||||
liveLeafCount: 0,
|
||||
runtimeProtocolVersion: 3,
|
||||
minCompatibleRuntimeClientVersion: 2,
|
||||
capabilities: []
|
||||
},
|
||||
_meta: { runtimeId: 'runtime-remote' }
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
clearRuntimeCompatibilityCacheForTests()
|
||||
reposList.mockReset()
|
||||
reposClone.mockReset()
|
||||
reposCloneRemote.mockReset()
|
||||
runtimeEnvironmentCall.mockReset()
|
||||
runtimeEnvironmentTransportCall.mockReset()
|
||||
runtimeEnvironmentTransportCall.mockImplementation((args: RuntimeEnvironmentCallRequest) => {
|
||||
if (args.method === 'status.get') {
|
||||
return runtimeStatusWithoutProjectHostSetup()
|
||||
}
|
||||
return runtimeEnvironmentCall(args)
|
||||
})
|
||||
vi.stubGlobal('window', {
|
||||
api: {
|
||||
repos: {
|
||||
list: reposList,
|
||||
clone: reposClone,
|
||||
cloneRemote: reposCloneRemote
|
||||
},
|
||||
runtimeEnvironments: { call: runtimeEnvironmentTransportCall }
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('repo slice project-host setup runtime capability', () => {
|
||||
it('falls back to repo-derived project setup state when a remote runtime lacks support', async () => {
|
||||
runtimeEnvironmentCall.mockResolvedValue({
|
||||
id: 'rpc-1',
|
||||
ok: true,
|
||||
result: { repos: [remoteRepo] },
|
||||
_meta: { runtimeId: 'runtime-remote' }
|
||||
})
|
||||
const store = createTestStore()
|
||||
store.setState({ settings: { activeRuntimeEnvironmentId: 'env-1' } as never })
|
||||
|
||||
await store.getState().fetchRepos()
|
||||
|
||||
expect(store.getState().projectHostSetups).toEqual([
|
||||
expect.objectContaining({ id: 'remote-repo', hostId: 'runtime:env-1' })
|
||||
])
|
||||
expect(runtimeEnvironmentCall).toHaveBeenCalledTimes(1)
|
||||
expect(runtimeEnvironmentCall).toHaveBeenCalledWith({
|
||||
selector: 'env-1',
|
||||
method: 'repo.list',
|
||||
params: undefined,
|
||||
timeoutMs: 15_000
|
||||
})
|
||||
})
|
||||
|
||||
it('blocks runtime project setup when the server does not advertise support', async () => {
|
||||
const store = createTestStore()
|
||||
|
||||
await expect(
|
||||
store.getState().setupProjectExistingFolder({
|
||||
projectId: 'project-1',
|
||||
hostId: 'runtime:env-1',
|
||||
path: '/srv/project',
|
||||
kind: 'git'
|
||||
})
|
||||
).resolves.toBeNull()
|
||||
|
||||
expect(runtimeEnvironmentCall).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('blocks runtime project clone before mutating unsupported servers', async () => {
|
||||
const store = createTestStore()
|
||||
|
||||
await expect(
|
||||
store.getState().setupProjectClone({
|
||||
projectId: 'project-1',
|
||||
hostId: 'runtime:env-1',
|
||||
url: 'https://github.com/stablyai/orca.git',
|
||||
destination: '/srv'
|
||||
})
|
||||
).resolves.toBeNull()
|
||||
|
||||
expect(runtimeEnvironmentCall).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
projectHostSetupProjectionFromRepos,
|
||||
type ProjectHostSetupProjection
|
||||
} from '../../../../shared/project-host-setup-projection'
|
||||
import { PROJECT_HOST_SETUP_RUNTIME_CAPABILITY } from '../../../../shared/protocol-version'
|
||||
import { isGitRepoKind } from '../../../../shared/repo-kind'
|
||||
import { sanitizeRepoIcon } from '../../../../shared/repo-icon'
|
||||
import { normalizeRepoBadgeColor } from '../../../../shared/repo-badge-color'
|
||||
@@ -27,7 +28,11 @@ import { getProjectGroupSubtreeIds } from '../../../../shared/project-groups'
|
||||
import { getRepoIdFromWorktreeId } from './worktree-helpers'
|
||||
import { reconcileFetchedRepos } from './repo-identity-reconcile'
|
||||
import { splitRepoReorderByHost } from './repo-reorder-host-split'
|
||||
import { callRuntimeRpc, getActiveRuntimeTarget } from '../../runtime/runtime-rpc-client'
|
||||
import {
|
||||
assertRuntimeEnvironmentCapability,
|
||||
callRuntimeRpc,
|
||||
getActiveRuntimeTarget
|
||||
} from '../../runtime/runtime-rpc-client'
|
||||
import { toRuntimeWorktreeSelector } from '../../runtime/runtime-worktree-selector'
|
||||
import { buildDismissedOnboardingFolderAgentStartup } from '@/lib/onboarding-folder-agent-startup'
|
||||
import { markOnboardingProjectAdded } from '@/lib/onboarding-project-checklist'
|
||||
@@ -184,6 +189,7 @@ async function fetchProjectHostSetupCompatibility(
|
||||
setups: await projectsApi.listHostSetups()
|
||||
}
|
||||
}
|
||||
await assertProjectHostSetupRuntimeCapability(target)
|
||||
const [projectResponse, setupResponse] = await Promise.all([
|
||||
callRuntimeRpc<{ projects: Project[] }>(target, 'project.list', undefined, {
|
||||
timeoutMs: 15_000
|
||||
@@ -203,6 +209,20 @@ async function fetchProjectHostSetupCompatibility(
|
||||
}
|
||||
}
|
||||
|
||||
async function assertProjectHostSetupRuntimeCapability(
|
||||
target: ReturnType<typeof getActiveRuntimeTarget>
|
||||
): Promise<void> {
|
||||
if (target.kind !== 'environment') {
|
||||
return
|
||||
}
|
||||
await assertRuntimeEnvironmentCapability(
|
||||
target.environmentId,
|
||||
PROJECT_HOST_SETUP_RUNTIME_CAPABILITY,
|
||||
'The selected Orca server does not support project host setup yet. Update Orca on the server and try again.',
|
||||
15_000
|
||||
)
|
||||
}
|
||||
|
||||
function projectCompatibilityFromRepos(
|
||||
repos: readonly Repo[]
|
||||
): Pick<RepoSlice, 'projects' | 'projectHostSetups'> {
|
||||
@@ -672,6 +692,7 @@ export const createRepoSlice: StateCreator<AppState, [], [], RepoSlice> = (set,
|
||||
setupProjectExistingFolder: async (args) => {
|
||||
try {
|
||||
const target = getProjectSetupRuntimeTarget(args.hostId)
|
||||
await assertProjectHostSetupRuntimeCapability(target)
|
||||
const result =
|
||||
target.kind === 'local'
|
||||
? await window.api.projects.setupExistingFolder(args)
|
||||
@@ -720,6 +741,9 @@ export const createRepoSlice: StateCreator<AppState, [], [], RepoSlice> = (set,
|
||||
try {
|
||||
const parsedHost = parseExecutionHostId(args.hostId)
|
||||
const target = getProjectSetupRuntimeTarget(args.hostId)
|
||||
if (parsedHost?.kind !== 'ssh') {
|
||||
await assertProjectHostSetupRuntimeCapability(target)
|
||||
}
|
||||
const repo =
|
||||
parsedHost?.kind === 'ssh'
|
||||
? await window.api.repos.cloneRemote({
|
||||
|
||||
@@ -1056,6 +1056,18 @@ describe('web file preload API', () => {
|
||||
).rejects.toThrow('Remote file download is unavailable in paired web clients.')
|
||||
})
|
||||
|
||||
it('rejects SSH clone requests in paired web clients', async () => {
|
||||
const { api } = await installApi('Linux')
|
||||
|
||||
await expect(
|
||||
api.repos.cloneRemote({
|
||||
connectionId: 'ssh-1',
|
||||
url: 'https://github.com/stablyai/orca.git',
|
||||
destination: '/workspace'
|
||||
})
|
||||
).rejects.toThrow('SSH clone is unavailable in paired web clients.')
|
||||
})
|
||||
|
||||
it('returns false for runtime missing-path errors from fs.pathExists', async () => {
|
||||
const runtimeCalls: { method: string; params: unknown }[] = []
|
||||
const worktree = {
|
||||
|
||||
@@ -997,6 +997,11 @@ function createReposApi(): NonNullable<Partial<PreloadApi>['repos']> {
|
||||
await callRuntimeResult<{ repo: Repo }>('repo.clone', { url, destination }, 10 * 60_000)
|
||||
).repo
|
||||
},
|
||||
cloneRemote: async () => {
|
||||
// Why: SSH relay cloning is owned by the desktop main process; paired web
|
||||
// clients must not pretend they can run that local IPC path directly.
|
||||
throw new Error('SSH clone is unavailable in paired web clients.')
|
||||
},
|
||||
cloneAbort: () => Promise.resolve(),
|
||||
addRemote: async ({ remotePath, displayName, kind }) => {
|
||||
invalidateRuntimeWorktreeCaches()
|
||||
|
||||
@@ -21,6 +21,8 @@ export const RUNTIME_PROTOCOL_VERSION = 3
|
||||
export const MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION = 2
|
||||
export const MIN_COMPATIBLE_RUNTIME_SERVER_VERSION = 3
|
||||
|
||||
export const PROJECT_HOST_SETUP_RUNTIME_CAPABILITY = 'project-host-setup.v1' as const
|
||||
|
||||
export const RUNTIME_CAPABILITIES = [
|
||||
'runtime.status.compat.v1',
|
||||
'runtime.environments.v1',
|
||||
@@ -28,7 +30,8 @@ export const RUNTIME_CAPABILITIES = [
|
||||
'terminal.binary-stream.v1',
|
||||
'terminal.multiplex.v1',
|
||||
'workspace-ports.v1',
|
||||
'mobile.tasks.v1'
|
||||
'mobile.tasks.v1',
|
||||
PROJECT_HOST_SETUP_RUNTIME_CAPABILITY
|
||||
] as const
|
||||
|
||||
export type RuntimeCapability = (typeof RUNTIME_CAPABILITIES)[number] | (string & {})
|
||||
|
||||
Reference in New Issue
Block a user