feat(runtime-host): add remote project registration - #3145
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
Included review availability: Your plan includes up to 3 reviews per rolling hour; 1 remains after this review. 📝 WalkthroughSummaryThis PR adds remote project registration for Desktop targets. Users can browse Host-published folders through a constrained picker and register a selected folder through the existing Project Catalog authority. Local project selection still uses the native OS picker. The implementation extends the existing Project Catalog, Runtime Host protocol, IPC bridge, and project-management service. It does not create a parallel registration path. Opaque root IDs, validated path segments, pagination limits, symlink checks, and canonical path boundaries restrict access to published roots. The solution is the smallest coherent path for the feature. The remote picker, capability flags, bridge methods, protocol validation, directory authority, and Host-scoped actions support remote selection without changing local behavior. No clear deletion or simplification opportunity is visible without reducing behavior or regression coverage. Reported validation includes lint, formatting, type checks, surface inventory checks, affected tests, and remote SSH verification. Full tests reportedly passed 951/952, with the existing timeout passing on isolated rerun. The current checkout returned no changed files, so the implementation and check results cannot be independently verified from the available diff. Review-relevant risks
WalkthroughThis change adds remote host-directory browsing and project registration. It extends runtime-host protocols, root configuration, Desktop IPC and preload APIs, storage boundaries, renderer project-selection flows, localization, styling, and validation tests. ChangesRemote directory selection
Estimated code review effort: 4 (Complex) | ~60 minutes Merge Risk: 🟡 Moderate · up to This PR adds remote folder browsing and project registration, but the current behavior leaves Relink enabled while doing nothing for some hosts and exposes root identifiers that may allow configured filesystem paths to be inferred. Merge should wait for these bounded correctness and security issues to be fixed or explicitly accepted. Sequence Diagram(s)sequenceDiagram
participant User
participant RemoteProjectDirectoryDialog
participant MakaBridge
participant ProjectManagementService
participant HostProjectDirectoryAuthority
participant ProjectCatalog
User->>RemoteProjectDirectoryDialog: Open remote directory picker
RemoteProjectDirectoryDialog->>MakaBridge: Request roots and directory entries
MakaBridge->>ProjectManagementService: Forward directory request
ProjectManagementService->>HostProjectDirectoryAuthority: Query validated path
HostProjectDirectoryAuthority-->>ProjectManagementService: Return directory page
ProjectManagementService-->>RemoteProjectDirectoryDialog: Return entries
User->>RemoteProjectDirectoryDialog: Register selected directory
RemoteProjectDirectoryDialog->>MakaBridge: Register directory
MakaBridge->>ProjectManagementService: Forward registration
ProjectManagementService->>HostProjectDirectoryAuthority: Resolve directory path
HostProjectDirectoryAuthority->>ProjectCatalog: Register resolved path within root
ProjectCatalog-->>RemoteProjectDirectoryDialog: Return registered project
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1⚔️ Resolve merge conflicts 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/app-shell.tsx (1)
1908-1912: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDo not expose relink for host-directory-only Hosts.
onRelinkis enabled whenchooseHostDirectoryis true.useNewTaskTarget.relinkProjectexits unlesschooseClientDirectoryis true. A Host that supports only remote directory selection shows an enabled Relink action that does nothing. KeeponAddenabled for either capability, but provideonRelinkonly whenchooseClientDirectoryis true.Proposed fix
- ...(host.capabilities.chooseClientDirectory || host.capabilities.chooseHostDirectory - ? { - onAdd: () => void newTask.addProject(host), - onRelink: (projectId: string) => void newTask.relinkProject(host, projectId), - } - : {}), + ...(host.capabilities.chooseClientDirectory || host.capabilities.chooseHostDirectory + ? { onAdd: () => void newTask.addProject(host) } + : {}), + ...(host.capabilities.chooseClientDirectory + ? { onRelink: (projectId: string) => void newTask.relinkProject(host, projectId) } + : {}),
🧹 Nitpick comments (2)
packages/runtime-host/src/__tests__/project-directory-authority.test.ts (1)
27-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider covering cursor continuation.
The test covers roots, hidden directories, symlink exclusion, and registration. It does not cover
directory_list_continue. The paging code inquerycomputesstartfrom the cursor and derivesnextCursorfrom the last accepted entry, so a regression there stays invisible. A case with enough sibling folders to force a second page, asserting that concatenated pages equal the full sorted list with no duplicate or missing name, would protect that behavior.apps/desktop/src/main/runtime-host-client.ts (1)
603-614: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRoute this mutation through
#mutateProject.Every other project mutation on this class goes through
#mutateProject. This one callsrequestdirectly. The input typeProjectCatalogMutateInputalready includes theregister_directoryvariant, so the helper accepts it. Using it keeps one call path for project mutations.♻️ Proposed refactor
async registerProjectDirectory( rootId: string, segments: readonly string[], ): Promise<ProjectCatalogProject> { return this.#projectForMutation( - await this.request("project.catalog.mutate", { - kind: "register_directory", - rootId, - segments, - }), + await this.#mutateProject({ kind: "register_directory", rootId, segments }), ); }
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 574fd81b-26b2-4f5b-860d-5652b0402349
📒 Files selected for processing (25)
apps/desktop/src/main/__tests__/app-shell-project-actions.test.tsapps/desktop/src/main/__tests__/project-management-service.test.tsapps/desktop/src/main/app-ipc-main.tsapps/desktop/src/main/project-management-service.tsapps/desktop/src/main/runtime-host-boot.tsapps/desktop/src/main/runtime-host-client.tsapps/desktop/src/main/runtime-host-project-catalog.tsapps/desktop/src/preload/bridge-contract.d.tsapps/desktop/src/preload/preload.tsapps/desktop/src/renderer/app-shell.tsxapps/desktop/src/renderer/locales/shell-copy.tsapps/desktop/src/renderer/remote-project-directory-dialog.tsxapps/desktop/src/renderer/settings/projects-settings-page.tsxapps/desktop/src/renderer/styles/composer.cssapps/desktop/src/renderer/use-new-task-target.tsapps/desktop/src/renderer/use-project-context.tsdocs/astryx-surface-file-inventory.mddocs/astryx-surface-file-inventory.pathspackages/runtime-host/src/__tests__/project-catalog-protocol.test.tspackages/runtime-host/src/__tests__/project-directory-authority.test.tspackages/runtime-host/src/protocol/index.tspackages/runtime-host/src/protocol/project-catalog.tspackages/runtime-host/src/server/project-catalog-coordinator.tspackages/runtime-host/src/server/project-directory-authority.tspackages/ui/src/workspace-picker.tsx
Included review availability: Your plan includes up to 3 reviews per rolling hour; 2 remain after this review.
There was a problem hiding this comment.
Pull request overview
Adds remote Runtime Host-side project directory browsing + registration so Desktop can register a new Project on a remote Host without ever handling arbitrary Host paths (only opaque root IDs + validated path segments). This fits into the ongoing Runtime Host hardening work by keeping Host path resolution and project registration authority on the Host while exposing a constrained UX to remote clients.
Changes:
- Introduces a HostProjectDirectoryAuthority and extends the project catalog protocol to support directory root + paged directory listing + “register selected directory” mutation.
- Adds a new Desktop remote directory picker dialog and wires it into both the new-task workspace picker flow and Projects settings.
- Updates Desktop/main IPC and runtime-host client/catalog plumbing to support directory queries + registration.
Reviewed changes
Copilot reviewed 24 out of 25 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/ui/src/workspace-picker.tsx | Adjusts workspace picker actions to better support per-group add/no-project options and retry. |
| packages/runtime-host/src/server/project-directory-authority.ts | Adds Host-side constrained directory browsing and registration path resolution under a published root. |
| packages/runtime-host/src/server/project-catalog-coordinator.ts | Routes new directory query/mutation kinds through the project catalog coordinator and adds error mapping. |
| packages/runtime-host/src/protocol/project-catalog.ts | Extends protocol types/limits and codecs for directory browsing + register_directory mutation. |
| packages/runtime-host/src/protocol/index.ts | Bumps compatibility epoch to reflect protocol changes. |
| packages/runtime-host/src/tests/project-directory-authority.test.ts | Adds Host authority tests for root containment and symlink escape prevention. |
| packages/runtime-host/src/tests/project-catalog-protocol.test.ts | Adds protocol/grant tests to ensure remote directory selection stays path-free and traversal is rejected. |
| docs/astryx-surface-file-inventory.paths | Adds the new remote directory dialog surface to Astryx inventory. |
| docs/astryx-surface-file-inventory.md | Updates Astryx inventory totals and includes the new dialog entry. |
| apps/desktop/src/renderer/use-project-context.ts | Extends DesktopProjectCapabilities with chooseHostDirectory default. |
| apps/desktop/src/renderer/use-new-task-target.ts | Adds remote directory picker state + acceptance flow for registered projects in new-task target selection. |
| apps/desktop/src/renderer/styles/composer.css | Adds styling for the remote project directory dialog layout. |
| apps/desktop/src/renderer/settings/projects-settings-page.tsx | Adds “Add project” behavior that opens remote directory picker when supported. |
| apps/desktop/src/renderer/remote-project-directory-dialog.tsx | New dialog UI for browsing host-published directories and registering a selected folder. |
| apps/desktop/src/renderer/locales/shell-copy.ts | Adds localized copy strings for the remote directory dialog UI. |
| apps/desktop/src/renderer/app-shell.tsx | Wires the remote directory dialog into the main shell overlay flow for new-task project actions. |
| apps/desktop/src/preload/preload.ts | Exposes new directory roots/list/register APIs on the Maka bridge. |
| apps/desktop/src/preload/bridge-contract.d.ts | Adds types + bridge method signatures for remote directory selection. |
| apps/desktop/src/main/runtime-host-project-catalog.ts | Extends the runtime-host project catalog adapter with directory browsing + registration functions. |
| apps/desktop/src/main/runtime-host-client.ts | Implements directory roots/listing/register operations against the Host project catalog protocol. |
| apps/desktop/src/main/runtime-host-boot.ts | Wires directory catalog into project management service and toggles capability flags for local vs remote. |
| apps/desktop/src/main/project-management-service.ts | Adds directory roots/list/register endpoints (Host-only) and IPC input validation for directory requests. |
| apps/desktop/src/main/app-ipc-main.ts | Adds IPC handlers for directory roots/list/register project directory operations. |
| apps/desktop/src/main/tests/project-management-service.test.ts | Adds coverage for remote Host directory actions and ensures client-local directory picker isn’t used remotely. |
| apps/desktop/src/main/tests/app-shell-project-actions.test.ts | Updates capability expectations to include chooseHostDirectory. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 50b39d04-2d75-43c1-909d-59f76645f929
📒 Files selected for processing (19)
apps/desktop/src/main/__tests__/project-management-service.test.tsapps/desktop/src/preload/bridge-contract.d.tsapps/desktop/src/renderer/remote-project-directory-dialog.tsxapps/desktop/src/renderer/styles/composer.cssdocs/architecture/runtime-host-architecture.mddocs/architecture/runtime-host-architecture.zh-CN.mddocs/runtime-host-remote-access.mddocs/runtime-host-remote-access.zh-CN.mdpackages/cli/src/__tests__/runtime-host-operator-command.test.tspackages/cli/src/cli.tspackages/cli/src/runtime-host-cli.tspackages/cli/src/runtime-host-service-command.tspackages/runtime-host/src/__tests__/project-catalog-protocol.test.tspackages/runtime-host/src/__tests__/project-directory-authority.test.tspackages/runtime-host/src/protocol/project-catalog.tspackages/runtime-host/src/server/execution-composition-factory.tspackages/runtime-host/src/server/execution-composition.tspackages/runtime-host/src/server/execution-service.tspackages/runtime-host/src/server/project-directory-authority.ts
🚧 Files skipped from review as they are similar to previous changes (7)
- apps/desktop/src/renderer/styles/composer.css
- packages/runtime-host/src/tests/project-catalog-protocol.test.ts
- packages/runtime-host/src/tests/project-directory-authority.test.ts
- apps/desktop/src/renderer/remote-project-directory-dialog.tsx
- packages/runtime-host/src/protocol/project-catalog.ts
- apps/desktop/src/main/tests/project-management-service.test.ts
- apps/desktop/src/preload/bridge-contract.d.ts
Included review availability: Your plan includes up to 3 reviews per rolling hour; 1 remains after this review.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 36 changed files in this pull request and generated no new comments.
Suppressed comments (4)
apps/desktop/src/renderer/remote-project-directory-dialog.tsx:128
register()can still callonRegisteredafter the dialog has been closed or the Host has changed (the component stays mounted, and there is no request-sequence guard likenavigate()/selectRoot()). This can register a project even if the user hit Cancel, and can update state for a stale Host.
async function register(): Promise<void> {
const host = props.host;
if (!host || !root || registering) return;
setRegistering(true);
setError(undefined);
try {
props.onRegistered(await window.maka.projects.registerDirectory({
rootId: root.id,
segments,
}, host));
} catch (cause) {
setError(localizedShellErrorMessage(cause, copy.projectUpdateFailedFallback, locale));
} finally {
setRegistering(false);
}
apps/desktop/src/main/project-management-service.ts:221
requireDirectoryInput()only checks basic shapes. Because this is an IPC boundary (renderer -> main), it should defensively validaterootId/segmentsagainst the same constraints as the Runtime Host protocol (max segments, per-segment size, and forbidding '.', '..', and path separators). Without this, a compromised renderer can send arbitrarily large/invalid payloads and force expensive encoding / repeated protocol failures.
function requireDirectoryInput(value: unknown): {
readonly rootId: string;
readonly segments: readonly string[];
} {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new TypeError('Invalid project directory');
}
const input = value as { rootId?: unknown; segments?: unknown };
if (
typeof input.rootId !== 'string' ||
!Array.isArray(input.segments) ||
!input.segments.every((segment) => typeof segment === 'string')
) {
throw new TypeError('Invalid project directory');
}
return { rootId: input.rootId, segments: input.segments };
apps/desktop/src/renderer/locales/shell-copy.ts:743
- The zh-CN label for hiding hidden folders is awkward/duplicated ("隐藏隐藏目录"). A clearer counterpart to "显示隐藏目录" is "不显示隐藏目录".
remoteDirectoryShowHidden: '显示隐藏目录',
remoteDirectoryHideHidden: '隐藏隐藏目录',
packages/cli/src/cli.ts:130
- The help text for
--project-rootdoesn't mention the absolute-path requirement, butparseProjectRoot()rejects non-absolute paths. Surfacing this constraint in--helpmakes the option self-explanatory.
' --project-root <label>=<path> Publish a project directory root (repeatable)',
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/runtime-host/src/server/project-directory-authority.ts (1)
168-171: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winUse non-derivable IDs for named roots.
Lines 168-171 derive each ID from an unkeyed hash of the absolute root path. A root-list client can hash plausible paths offline and recover configured filesystem locations. This conflicts with opaque root IDs.
Generate a random ID when the authority is constructed. Keep it only in the authority root map.
As per path instructions, review the diff adversarially against the problem it claims to solve.
Source: Path instructions
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 1db1e8a8-ea0b-40b5-9846-5e3bcd6d65b8
📒 Files selected for processing (14)
apps/desktop/src/main/runtime-host-client.tsapps/desktop/src/renderer/app-shell.tsxapps/desktop/src/renderer/locales/shell-copy.tsapps/desktop/src/renderer/remote-project-directory-dialog.tsxapps/desktop/src/renderer/use-new-task-target.tspackages/cli/src/cli.tspackages/runtime-host/src/__tests__/project-catalog-coordinator.test.tspackages/runtime-host/src/__tests__/project-directory-authority.test.tspackages/runtime-host/src/protocol/project-catalog.tspackages/runtime-host/src/server/project-catalog-coordinator.tspackages/runtime-host/src/server/project-directory-authority.tspackages/storage/src/__tests__/project-catalog.test.tspackages/storage/src/project-catalog-authority.tspackages/storage/src/project-catalog.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- packages/cli/src/cli.ts
- apps/desktop/src/renderer/app-shell.tsx
- apps/desktop/src/main/runtime-host-client.ts
- apps/desktop/src/renderer/use-new-task-target.ts
- apps/desktop/src/renderer/locales/shell-copy.ts
Included review availability: Your plan includes up to 3 reviews per rolling hour; 0 remain after this review.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 39 out of 40 changed files in this pull request and generated no new comments.
Suppressed comments (1)
apps/desktop/src/renderer/remote-project-directory-dialog.tsx:174
aria-labelfor the breadcrumb<nav>is set tocopy.currentProject(localized as “Current project”), but this UI represents the current directory path. This label will be incorrect for screen readers and should have a dedicated localized string (e.g. “Current folder” / “当前文件夹”).
<LayoutContent padding={4}>
<div className="remoteProjectDirectoryBody">
<nav className="remoteProjectDirectoryBreadcrumbs" aria-label={copy.currentProject}>
{roots.length > 1 ? (
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/runtime-host/src/__tests__/project-directory-authority.test.ts (1)
37-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDo not hard-code opaque root ID values unless the protocol guarantees them.
root-1androot-2lock this test to the current allocation format. They do not verify path-independent stability. Assert that IDs are opaque, unique, and stable for equivalent published roots with different absolute paths. Keep the exact values only if the protocol explicitly guarantees these strings.As per path instructions, tests must protect observable behavior and must not assert implementation details.
Source: Path instructions
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 42afad22-d272-461b-961b-9ed6b7a340f6
📒 Files selected for processing (6)
apps/desktop/src/main/runtime-host-client.tsapps/desktop/src/renderer/locales/shell-copy.tsapps/desktop/src/renderer/remote-project-directory-dialog.tsxpackages/runtime-host/src/__tests__/project-directory-authority.test.tspackages/runtime-host/src/server/project-catalog-coordinator.tspackages/runtime-host/src/server/project-directory-authority.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- apps/desktop/src/renderer/locales/shell-copy.ts
- apps/desktop/src/main/runtime-host-client.ts
- apps/desktop/src/renderer/remote-project-directory-dialog.tsx
- packages/runtime-host/src/server/project-directory-authority.ts
- packages/runtime-host/src/server/project-catalog-coordinator.ts
Included review availability: Your plan includes up to 3 reviews per rolling hour; 2 remain after this review.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 39 out of 40 changed files in this pull request and generated no new comments.
Suppressed comments (1)
packages/runtime-host/src/server/project-directory-authority.ts:89
#resolveDirectorythrows "Project directory is outside the published root" both when the resolved path escapes the root and when the path is inside the root but is not a directory. That makes invalid-request responses misleading (e.g., selecting a file under the root would report an out-of-root error). Split the checks so the error message matches the actual failure mode.
Allow remote Desktop targets to browse Host-published directory roots and register a selected directory without exposing arbitrary Host paths to the Client. Keep local project selection on the native OS picker and route remote registration through the existing Project Catalog authority. Generated-by: OpenAI Codex
Render project actions inside their owning Host group so remote registration remains reachable even when another Host is selected. Keep the remote home view focused by omitting hidden directories from enumeration. Generated-by: OpenAI Codex
Use Astryx controls for every directory and breadcrumb action, then register the new dialog in the generated surface inventory. This keeps the remote picker at zero surface blockers. Generated-by: OpenAI Codex
Enumerate every contained directory published by the Host, including dot-prefixed names. Visibility is a presentation concern and must not silently narrow Project registration authority. Generated-by: OpenAI Codex
Keep all Host directories reachable while hiding dot-prefixed entries by default in the Desktop picker. Users can reveal them explicitly without changing the Host authorization boundary. Generated-by: OpenAI Codex
Replace the labeled switch with an accessible eye icon button while preserving the same explicit show and hide behavior. Generated-by: OpenAI Codex
Keep the picker focused on navigation and selection. Remove explanatory implementation details that do not help users choose a project directory. Generated-by: OpenAI Codex
Preserve the service user's home directory as the default while allowing operators to replace it with a bounded, named root allowlist. Remote Clients browse only opaque published roots, and Desktop can switch between them without learning unrestricted Host paths. Generated-by: OpenAI Codex
Validate the final canonical Project path against its published root and keep pre-commit directory failures out of the Host drain path. Bound directory traversal and keep picker retries and late completions scoped to their exact Host. Generated-by: OpenAI Codex
Keep optional directory browsing from blocking Host startup, make published root identifiers path-independent, and bound each enumeration before sorting. Align the Desktop picker’s navigation and retry behavior with the exact Host operation. Generated-by: OpenAI Codex
Report non-directory selections separately from root escapes, and keep root identity tests focused on the protocol's uniqueness contract instead of its current allocation format. Generated-by: OpenAI Codex
065dc74 to
0c47963
Compare
|
I have manually reviewed the changes in this PR (the CI issue was not introduced by the changes in this PR, and has already been reported as #3177), take full responsibility for this, and decide to merge it. |
`goal.arm` is a new wire operation, so a client that has it and a Host that does not are no longer safely interoperable. Without a new epoch the pair completes the handshake, the client offers Set Goal, and the first arm reaches an older decoder as an unknown operation — a protocol error that tears the connection down instead of refusing the pair up front and routing the user to the Host upgrade path. The epoch is the boundary the repository already uses for this: apache#3145 advanced it for a new operation and apache#3165 advanced it merely for removing catalog filters. Handshake fixtures name deliberately stale epochs to exercise rejection, so they stay as they are. Generated-by: Claude Code
`goal.arm` is a new wire operation, so a client that has it and a Host that does not are no longer safely interoperable. Without a new epoch the pair completes the handshake, the client offers Set Goal, and the first arm reaches an older decoder as an unknown operation — a protocol error that tears the connection down instead of refusing the pair up front and routing the user to the Host upgrade path. The epoch is the boundary the repository already uses for this: apache#3145 advanced it for a new operation and apache#3165 advanced it merely for removing catalog filters. Handshake fixtures name deliberately stale epochs to exercise rejection, so they stay as they are. Generated-by: Claude Code
`goal.arm` is a new wire operation, so a client that has it and a Host that does not are no longer safely interoperable. Without a new epoch the pair completes the handshake, the client offers Set Goal, and the first arm reaches an older decoder as an unknown operation — a protocol error that tears the connection down instead of refusing the pair up front and routing the user to the Host upgrade path. The epoch is the boundary the repository already uses for this: apache#3145 advanced it for a new operation and apache#3165 advanced it merely for removing catalog filters. Handshake fixtures name deliberately stale epochs to exercise rejection, so they stay as they are. Generated-by: Claude Code
`goal.arm` is a new wire operation, so a client that has it and a Host that does not are no longer safely interoperable. Without a new epoch the pair completes the handshake, the client offers Set Goal, and the first arm reaches an older decoder as an unknown operation — a protocol error that tears the connection down instead of refusing the pair up front and routing the user to the Host upgrade path. The epoch is the boundary the repository already uses for this: apache#3145 advanced it for a new operation and apache#3165 advanced it merely for removing catalog filters. Handshake fixtures name deliberately stale epochs to exercise rejection, so they stay as they are. Generated-by: Claude Code
`goal.arm` is a new wire operation, so a client that has it and a Host that does not are no longer safely interoperable. Without a new epoch the pair completes the handshake, the client offers Set Goal, and the first arm reaches an older decoder as an unknown operation — a protocol error that tears the connection down instead of refusing the pair up front and routing the user to the Host upgrade path. The epoch is the boundary the repository already uses for this: apache#3145 advanced it for a new operation and apache#3165 advanced it merely for removing catalog filters. Handshake fixtures name deliberately stale epochs to exercise rejection, so they stay as they are. Generated-by: Claude Code
`goal.arm` is a new wire operation, so a client that has it and a Host that does not are no longer safely interoperable. Without a new epoch the pair completes the handshake, the client offers Set Goal, and the first arm reaches an older decoder as an unknown operation — a protocol error that tears the connection down instead of refusing the pair up front and routing the user to the Host upgrade path. The epoch is the boundary the repository already uses for this: apache#3145 advanced it for a new operation and apache#3165 advanced it merely for removing catalog filters. Handshake fixtures name deliberately stale epochs to exercise rejection, so they stay as they are. Generated-by: Claude Code
`goal.arm` is a new wire operation, so a client that has it and a Host that does not are no longer safely interoperable. Without a new epoch the pair completes the handshake, the client offers Set Goal, and the first arm reaches an older decoder as an unknown operation — a protocol error that tears the connection down instead of refusing the pair up front and routing the user to the Host upgrade path. The epoch is the boundary the repository already uses for this: apache#3145 advanced it for a new operation and apache#3165 advanced it merely for removing catalog filters. Handshake fixtures name deliberately stale epochs to exercise rejection, so they stay as they are. Generated-by: Claude Code
`goal.arm` is a new wire operation, so a client that has it and a Host that does not are no longer safely interoperable. Without a new epoch the pair completes the handshake, the client offers Set Goal, and the first arm reaches an older decoder as an unknown operation — a protocol error that tears the connection down instead of refusing the pair up front and routing the user to the Host upgrade path. The epoch is the boundary the repository already uses for this: apache#3145 advanced it for a new operation and apache#3165 advanced it merely for removing catalog filters. Handshake fixtures name deliberately stale epochs to exercise rejection, so they stay as they are. Generated-by: Claude Code

English
Summary
Remote Desktop targets could select only Projects already registered on the Runtime Host. This PR adds a constrained Host-side directory picker so users can browse folders published by that Host and register one through the existing Project Catalog authority.
The service user's Home is published by default. Operators can replace it with up to eight named
--project-root <label>=<absolute-path>entries. Remote browsing uses opaque root IDs and validated path segments; Local keeps the native OS picker.Refs #2522
Verification
npm run lint— passednpm run format:check— passednpm run typecheck— passed across all workspacesnpm run astryx:surface-inventory— passed with 188 aligned surfaces and 0 blockersnpm test— 951/952 passed; the existing Candidate-arbitration timeout passed when rerun in isolationAI use
Tool(s) and scope: OpenAI Codex contributed implementation, tests, analysis, and remote Desktop verification under maintainer direction
Checklist
Does this PR entail a change in behavior?
简体中文
摘要
此前,Desktop 的远程目标只能选择已经在 Runtime Host 注册的 Project。本 PR 增加受限的 Host-side 目录选择器,让用户浏览该 Host 发布的文件夹,并通过现有 Project Catalog authority 注册所选目录。
默认发布运行服务用户的 Home。Operator 可以用最多八个命名的
--project-root <label>=<absolute-path>替换默认范围。远程浏览只使用不透明 root ID 和经过校验的路径分段;Local 继续使用系统原生选择器。关联 #2522
验证
npm run lint— 通过npm run format:check— 通过npm run typecheck— 所有 workspace 通过npm run astryx:surface-inventory— 188 个 surface 全部 aligned,0 blockernpm test— 951/952 通过;既有 Candidate 仲裁超时在隔离重跑时通过AI 使用
工具与范围:OpenAI Codex 在维护者指导下参与了实现、测试、分析和远程 Desktop 验证
检查清单
本 PR 是否会改变行为?