Skip to content

Commit 13f677e

Browse files
committed
feat(remote): add native Windows WSL workspaces
1 parent f85b64e commit 13f677e

44 files changed

Lines changed: 1297 additions & 142 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎docs/architecture/remote-workspace-transport.md‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Remote workspace transport
22

3-
This document defines the transport boundary for SSH and Docker workspaces.
3+
This document defines the transport boundary for SSH, Docker, and WSL workspaces.
44
The Agent Runtime stays on the OpenBitFun host. Local and remote workspaces share
55
file-tool algorithms through Session-bound IO providers; search retains native
66
acceleration with shared matching and reduction. The convergence section below
@@ -59,6 +59,30 @@ published `22/tcp` endpoint:
5959
Runtime code only reads `effective_config`. The original `auto` value remains
6060
persisted so a later reconnect can discover that sshd has become available.
6161

62+
## Native WSL
63+
64+
WSL is an independent workspace target. The additive `wsl` profile field stores
65+
an explicit distribution and optional Linux user. Missing `wsl` preserves legacy
66+
SSH/Docker behavior. Reserved `wsl.invalid:0` legacy endpoint fields keep older
67+
readers from treating a WSL profile as a usable SSH endpoint. Connection drift
68+
and saved-profile deduplication include the WSL target.
69+
70+
The Services integration launches `wsl.exe --distribution … --cd ~ --exec
71+
/bin/sh -lc …` on the owning Windows host through the managed command factory.
72+
Each argument stays separate; command stdout and file streams remain binary.
73+
Only Windows-side WSL listing and diagnostic output is decoded as UTF-16LE when
74+
needed. Interactive terminals use the existing local PTY adapter and WSL's
75+
configured default shell, with a POSIX cwd. Non-TTY commands share Docker's
76+
in-target PID/process-group supervision and separate signal channel.
77+
78+
WSL uses shell filesystem operations, never SFTP or Windows-local path IO.
79+
The host advertises `wsl_workspaces_v1`; controllers reject WSL discovery and
80+
profile mutations before RPC if the peer lacks that capability. Discovery then
81+
reports the executing host's platform support. CLI peer setup is explicitly
82+
unsupported by the Product Operation Registry. Existing Remote Control sessions
83+
reuse the bound workspace providers, and Detached Dispatch does not create WSL
84+
profiles. Native WSL has no SSH transport for port forwarding.
85+
6286
## ProxyJump
6387

6488
The comma-separated jump chain is resolved left to right. Each token can be a

‎docs/features/remote-workspaces.md‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Remote SSH and container workspaces
1+
# Remote SSH, container, and WSL workspaces
22

33
OpenBitFun remote workspaces use one saved target for the file explorer, terminal,
44
Agent commands, and workspace tools. The target can be:
@@ -7,14 +7,22 @@ Agent commands, and workspace tools. The target can be:
77
- an SSH host reached through one or more jump hosts;
88
- a Docker container on an SSH host;
99
- a Docker container on the local machine; or
10-
- an sshd endpoint running inside a container.
10+
- an sshd endpoint running inside a container; or
11+
- a WSL Linux distribution on the Windows OpenBitFun host.
1112

1213
The local client behavior is supported on macOS, Windows, and Linux. Remote
1314
workspace paths are always interpreted with POSIX `/` separators, independent
1415
of the client OS. Docker workspace commands require a POSIX-compatible
1516
container shell; selecting a Windows container does not silently reinterpret
1617
paths or commands with Windows semantics.
1718

19+
## Windows WSL
20+
21+
Choose **Windows WSL** as its own workspace target. OpenBitFun discovers installed
22+
distributions on the executing Windows host and connects through `wsl.exe`.
23+
See [Desktop WSL setup](../../src/apps/desktop/README.md#windows-wsl-workspaces)
24+
for prerequisites, user selection, and remote-surface support.
25+
1826
## Jump hosts
1927

2028
`ProxyJump` accepts a comma-separated chain such as `jump1,jump2` or

‎docs/interactive-capabilities/README.md‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<!-- generated by scripts/generate-interactive-capabilities.mjs; do not edit -->
22
# OpenBitFun 功能与设置目录 / OpenBitFun Features & Settings
33

4-
OpenBitFun Playbook 当前包含 **22 个功能**和 **21 个设置页**,共 **43 个**用户可理解的条目、**320 项**有源码证据的子能力。每个条目有独立 Markdown,并直接服务于说明书网站、OpenBitFun 全局搜索和 `OpenBitFunControl` Agent 工具。
4+
OpenBitFun Playbook 当前包含 **22 个功能**和 **21 个设置页**,共 **43 个**用户可理解的条目、**321 项**有源码证据的子能力。每个条目有独立 Markdown,并直接服务于说明书网站、OpenBitFun 全局搜索和 `OpenBitFunControl` Agent 工具。
55

6-
OpenBitFun Playbook currently contains **22 features**, **21 settings pages**, and **320** source-backed sub-capabilities across **43** user-facing entries. Every entry has its own Markdown page and directly powers the website, in-app global search, and the `OpenBitFunControl` agent tool.
6+
OpenBitFun Playbook currently contains **22 features**, **21 settings pages**, and **321** source-backed sub-capabilities across **43** user-facing entries. Every entry has its own Markdown page and directly powers the website, in-app global search, and the `OpenBitFunControl` agent tool.
77

88
## 唯一事实源 / Single source of truth
99

@@ -27,20 +27,20 @@ OpenBitFun Playbook currently contains **22 features**, **21 settings pages**, a
2727
- Generated per-item interaction audit: `docs/interactive-capabilities/technical/product-control-open-audit.json`
2828
- Generated low-level audit map: `docs/interactive-capabilities/technical/tauri-command-map.json`
2929

30-
说明书、网站、搜索和 Agent 只看“功能 + 设置 + 子能力”。每项子能力都必须引用已注册 Tauri Command 或可解析的源码标记;这些证据不会进入公开目录。当前 **665** 个 Tauri 命令只用于实现覆盖审计。产品 UI 交互源码会在生成和检查时扫描并校验,但不会保存成随普通 UI 改动频繁变化的版本化快照。
30+
说明书、网站、搜索和 Agent 只看“功能 + 设置 + 子能力”。每项子能力都必须引用已注册 Tauri Command 或可解析的源码标记;这些证据不会进入公开目录。当前 **666** 个 Tauri 命令只用于实现覆盖审计。产品 UI 交互源码会在生成和检查时扫描并校验,但不会保存成随普通 UI 改动频繁变化的版本化快照。
3131

32-
Docs, website, search, and agents see only features, settings, and documented sub-capabilities. Every sub-capability must reference a registered Tauri command or a resolvable source marker; evidence is stripped from public projections. The **665** Tauri commands remain implementation-audit evidence only. Product UI interaction sources are scanned and validated during generation and checks, but are not stored as a versioned snapshot that churns with ordinary UI changes.
32+
Docs, website, search, and agents see only features, settings, and documented sub-capabilities. Every sub-capability must reference a registered Tauri command or a resolvable source marker; evidence is stripped from public projections. The **666** Tauri commands remain implementation-audit evidence only. Product UI interaction sources are scanned and validated during generation and checks, but are not stored as a versioned snapshot that churns with ordinary UI changes.
3333

3434
## 控制边界 / Control boundary
3535

36-
- 每个子能力都明确标记为直接控制、委托给专用 Agent 工具、需交互打开或不支持;“打开页面”不会再被统计成“Agent 已控制”。当前覆盖:直接 **48**、委托 **61**、需交互 **211**、不支持 **0**。
36+
- 每个子能力都明确标记为直接控制、委托给专用 Agent 工具、需交互打开或不支持;“打开页面”不会再被统计成“Agent 已控制”。当前覆盖:直接 **48**、委托 **61**、需交互 **212**、不支持 **0**。
3737
- 稳定行为声明为带 JSON 输入契约的 `operations` 或 `options`,并绑定原生产品控制 Provider;Agent 不接触原始 Tauri Command。
38-
- `OpenBitFunControl list` 和 `search` 都返回带 `nextCursor` 的精简分页结果;目录可持续增长,不靠固定总量上限。完整目录和 320 项子能力都不会写入 system prompt。
38+
- `OpenBitFunControl list` 和 `search` 都返回带 `nextCursor` 的精简分页结果;目录可持续增长,不靠固定总量上限。完整目录和 321 项子能力都不会写入 system prompt。
3939
- 目录发现与契约读取不依赖 React 或可见窗口。普通配置型 option 统一由 Product Assembly 的共享 ConfigService 执行器读、写并回读,因此 Desktop、CLI 与 Headless 表面走同一份实现;只有宿主原生 operation/provider option 和界面导航按表面注册适配器,缺失时必须明确返回不可用,禁止静默回退本机。只读 Agent 只能发现和读取目录。
4040

41-
- Every documented item is classified as direct control, delegated Agent control, interactive opening, or unsupported; opening a page is never counted as direct control. Current coverage is **48 direct**, **61 delegated**, **211 interactive**, and **0 unsupported**.
41+
- Every documented item is classified as direct control, delegated Agent control, interactive opening, or unsupported; opening a page is never counted as direct control. Current coverage is **48 direct**, **61 delegated**, **212 interactive**, and **0 unsupported**.
4242
- Stable behavior becomes a typed `operation` or `option` with a JSON input contract and a native product-control provider. Agents never receive raw Tauri commands.
43-
- `OpenBitFunControl list` and `search` return compact pages with a `nextCursor`; the catalog can grow without a fixed total-size ceiling. Neither the full catalog nor its 320 documented items enters the system prompt.
43+
- `OpenBitFunControl list` and `search` return compact pages with a `nextCursor`; the catalog can grow without a fixed total-size ceiling. Neither the full catalog nor its 321 documented items enters the system prompt.
4444
- Discovery and contract lookup do not depend on React or a visible window. Ordinary config-backed options are read, written, and read back by one Product Assembly ConfigService executor shared by Desktop, CLI, and headless surfaces. Only host-native operations/provider options and presentation routes install surface adapters; missing adapters return explicit unavailability without local fallback. Read-only agents may only discover and inspect entries.
4545

4646
## 防腐化门禁 / Anti-drift gates

‎docs/interactive-capabilities/capabilities.json‎

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"title": "OpenBitFun Playbook",
55
"origin": "https://playbook.openbitfun.com",
66
"source": "src/shared/interactive-capabilities/catalog.json",
7-
"digest": "9973af8307e72206bb5c4af5dfb3249fef2def6a27925a13aca9a25ac62ef8a3",
7+
"digest": "9a3d11cb4a198369ceb62ae82e1d0e1e6bddb658f4a790ec84e9eb4123803e5a",
88
"ownerDigest": "c0e5c187cf62bc6ed06196ce8520b3eb427bf268cf24659b72d2552fb1d99c54",
99
"searchAcceptance": [
1010
{
@@ -138,11 +138,11 @@
138138
"features": 22,
139139
"settings": 21,
140140
"userFacing": 43,
141-
"documentedItems": 320,
141+
"documentedItems": 321,
142142
"controlCoverage": {
143143
"direct": 48,
144144
"delegated": 61,
145-
"interactive": 211,
145+
"interactive": 212,
146146
"unsupported": 0
147147
}
148148
},
@@ -8145,6 +8145,7 @@
81458145
"ssh-profiles",
81468146
"ssh-auth",
81478147
"docker",
8148+
"wsl",
81488149
"remote-open",
81498150
"remote-files",
81508151
"transfer",
@@ -8329,6 +8330,52 @@
83298330
"actionId": "project.new"
83308331
}
83318332
},
8333+
{
8334+
"id": "feature.remote-workspaces:open:wsl",
8335+
"capabilityId": "feature.remote-workspaces",
8336+
"itemIds": [
8337+
"wsl"
8338+
],
8339+
"kind": "open",
8340+
"risk": "ui",
8341+
"executionHost": "presentationSurface",
8342+
"availability": {
8343+
"desktop": {
8344+
"available": true
8345+
},
8346+
"cli": {
8347+
"available": false,
8348+
"reason": "This delivery profile has no live presentation surface"
8349+
},
8350+
"peer": {
8351+
"available": true,
8352+
"requiredCapabilities": [
8353+
"product_control_v1",
8354+
"product_control_presentation_v1"
8355+
]
8356+
},
8357+
"remoteControl": {
8358+
"available": true
8359+
},
8360+
"detachedDispatch": {
8361+
"available": false,
8362+
"reason": "This delivery profile has no live presentation surface"
8363+
}
8364+
},
8365+
"inputSchema": {
8366+
"type": "object",
8367+
"additionalProperties": false
8368+
},
8369+
"outputSchema": {
8370+
"type": "object",
8371+
"additionalProperties": true
8372+
},
8373+
"openReason": "unstructuredInteraction",
8374+
"presentationTarget": {
8375+
"kind": "action",
8376+
"actionId": "project.new"
8377+
}
8378+
},
83328379
{
83338380
"id": "feature.remote-workspaces:open:remote-open",
83348381
"capabilityId": "feature.remote-workspaces",
@@ -23508,6 +23555,17 @@
2350823555
"reasonEn": "“Discover remote Docker containers and use a container as the work environment” spans multiple live-state-dependent steps and currently has no single structured Command that can deterministically complete the whole workflow; the Agent opens the exact entry and keeps the remaining interaction visible to the user."
2350923556
}
2351023557
},
23558+
{
23559+
"id": "wsl",
23560+
"titleZh": "选择 Windows 主机上的 WSL 发行版作为工作区",
23561+
"titleEn": "Choose a WSL distribution on the Windows host as a workspace",
23562+
"control": {
23563+
"kind": "open",
23564+
"reasonCode": "unstructuredInteraction",
23565+
"reasonZh": "打开远程连接对话框,由用户选择发行版、Linux 用户和工作区目录。",
23566+
"reasonEn": "Open the remote connection dialog for the user to choose a distribution, Linux user, and workspace directory."
23567+
}
23568+
},
2351123569
{
2351223570
"id": "remote-open",
2351323571
"titleZh": "打开、关闭和移除远程工作区,并读取服务器信息",
@@ -23660,6 +23718,8 @@
2366023718
"Connect with passwords, private keys, certificates, SSH config hosts, and jump hosts",
2366123719
"发现远程 Docker 容器并把容器作为工作环境",
2366223720
"Discover remote Docker containers and use a container as the work environment",
23721+
"选择 Windows 主机上的 WSL 发行版作为工作区",
23722+
"Choose a WSL distribution on the Windows host as a workspace",
2366323723
"打开、关闭和移除远程工作区,并读取服务器信息",
2366423724
"Open, close, and remove remote workspaces, and inspect server information",
2366523725
"浏览、读取、写入、创建、重命名和删除远程文件与目录",

‎docs/interactive-capabilities/capabilities/feature.remote-workspaces.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Open projects over SSH or in containers so files, search, terminal, and agents a
2323
- Connect with passwords, private keys, certificates, SSH config hosts, and jump hosts
2424
- **Agent 可定位入口,需交互完成 / Agent opens; interaction required** · 发现远程 Docker 容器并把容器作为工作环境
2525
- Discover remote Docker containers and use a container as the work environment
26+
- **Agent 可定位入口,需交互完成 / Agent opens; interaction required** · 选择 Windows 主机上的 WSL 发行版作为工作区
27+
- Choose a WSL distribution on the Windows host as a workspace
2628
- **Agent 可定位入口,需交互完成 / Agent opens; interaction required** · 打开、关闭和移除远程工作区,并读取服务器信息
2729
- Open, close, and remove remote workspaces, and inspect server information
2830
- **由专用 Agent 工具控制 / Delegated Agent tool** · `LS` / `Read` / `Write` / `Edit` / `Delete` / `Glob` / `Grep` / `ExecCommand` · 浏览、读取、写入、创建、重命名和删除远程文件与目录

‎docs/interactive-capabilities/technical/product-control-open-audit.json‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
22
"schemaVersion": 1,
33
"generatedFrom": "src/shared/interactive-capabilities/catalog.json",
4-
"catalogDigest": "9973af8307e72206bb5c4af5dfb3249fef2def6a27925a13aca9a25ac62ef8a3",
5-
"count": 211,
4+
"catalogDigest": "9a3d11cb4a198369ceb62ae82e1d0e1e6bddb658f4a790ec84e9eb4123803e5a",
5+
"count": 212,
66
"reasonCounts": {
77
"externalAuth": 4,
88
"secretEntry": 5,
9-
"unstructuredInteraction": 184,
9+
"unstructuredInteraction": 185,
1010
"visualSelection": 18
1111
},
1212
"entries": [
@@ -1771,6 +1771,22 @@
17711771
"command:ssh_list_docker_containers"
17721772
]
17731773
},
1774+
{
1775+
"capabilityId": "feature.remote-workspaces",
1776+
"itemId": "wsl",
1777+
"titleZh": "选择 Windows 主机上的 WSL 发行版作为工作区",
1778+
"titleEn": "Choose a WSL distribution on the Windows host as a workspace",
1779+
"reasonCode": "unstructuredInteraction",
1780+
"reasonZh": "打开远程连接对话框,由用户选择发行版、Linux 用户和工作区目录。",
1781+
"reasonEn": "Open the remote connection dialog for the user to choose a distribution, Linux user, and workspace directory.",
1782+
"presentationTarget": {
1783+
"kind": "action",
1784+
"actionId": "project.new"
1785+
},
1786+
"evidence": [
1787+
"command:ssh_list_wsl_distributions"
1788+
]
1789+
},
17741790
{
17751791
"capabilityId": "feature.remote-workspaces",
17761792
"itemId": "remote-open",

‎docs/interactive-capabilities/technical/tauri-command-map.json‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"schemaVersion": 2,
33
"generatedFrom": "src/shared/interactive-capabilities/catalog.json",
4-
"catalogDigest": "9973af8307e72206bb5c4af5dfb3249fef2def6a27925a13aca9a25ac62ef8a3",
5-
"commandCount": 665,
4+
"catalogDigest": "9a3d11cb4a198369ceb62ae82e1d0e1e6bddb658f4a790ec84e9eb4123803e5a",
5+
"commandCount": 666,
66
"coverage": {
7-
"commandCount": 665,
8-
"documentedCommandCount": 632,
7+
"commandCount": 666,
8+
"documentedCommandCount": 633,
99
"implementationCommandCount": 33,
1010
"implementationDigest": "35539d9c1510287cb47f4a68fe35859b78f93bb06cd66b86e58d878a48d8c509"
1111
},
@@ -9546,6 +9546,22 @@
95469546
"signature": "fn ssh_list_saved_connections( state: State<'_, AppState>, ) -> Result<Vec<SavedConnection>, String>",
95479547
"remoteWorkspacePolicy": "WorkspaceAgnostic"
95489548
},
9549+
{
9550+
"id": "ssh_list_wsl_distributions",
9551+
"moduleId": "ssh",
9552+
"capabilityId": "feature.remote-workspaces",
9553+
"capabilityIds": [
9554+
"feature.remote-workspaces"
9555+
],
9556+
"documentedItemIds": [
9557+
"feature.remote-workspaces:wsl"
9558+
],
9559+
"visibility": "documented",
9560+
"rustPath": "api::ssh_api::ssh_list_wsl_distributions",
9561+
"sourceFile": "src/apps/desktop/src/api/ssh_api.rs",
9562+
"signature": "fn ssh_list_wsl_distributions( ) -> Result<openbitfun_core::service::remote_ssh::WslDistributions, String>",
9563+
"remoteWorkspacePolicy": "WorkspaceAgnostic"
9564+
},
95499565
{
95509566
"id": "ssh_save_connection",
95519567
"moduleId": "ssh",

‎src/apps/desktop/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ openbitfun-relay-service = { path = "../../crates/services/relay-service" }
2424
openbitfun-agent-runtime = { path = "../../crates/execution/agent-runtime", features = ["agent-runtime"] }
2525
openbitfun-runtime-ports = { path = "../../crates/contracts/runtime-ports", features = ["agent-api", "permission", "workspace-ports"] }
2626
openbitfun-product-domains = { path = "../../crates/contracts/product-domains", features = ["appearance-market"] }
27-
openbitfun-services-integrations = { path = "../../crates/services/services-integrations", features = ["canvas-runtime", "miniapp-market", "speech-realtime"] }
27+
openbitfun-services-integrations = { path = "../../crates/services/services-integrations", features = ["canvas-runtime", "miniapp-market", "remote-ssh-concrete", "speech-realtime"] }
2828
openbitfun-core-types = { path = "../../crates/contracts/core-types" }
2929
openbitfun-agent-tools = { path = "../../crates/execution/tool-contracts", features = ["element-token"] }
3030
openbitfun-transport = { path = "../../crates/adapters/transport", features = ["tauri-adapter"] }

0 commit comments

Comments
 (0)