ds-harness-remote 0.4.8 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,207 +1,102 @@
1
- # ds-harness-remote
1
+ # DeepSeek Harness Remote
2
2
 
3
- English | 中文
3
+ Continue your DeepSeek Harness sessions and experimental Codex workspaces from another device over an end-to-end encrypted connection.
4
4
 
5
- ## Overview / 概览
5
+ [GitHub](https://github.com/liguobao/ds-harness-remote) · [Full guide](https://github.com/liguobao/ds-harness-remote#readme) · [中文说明](https://github.com/liguobao/ds-harness-remote/blob/main/README.zh.md) · [Remote Web](https://dsh.r2049.cn/app) · [Android](https://github.com/liguobao/ds-harness-remote/releases/latest)
6
6
 
7
- DeepSeek Remote plugin for DSH Host + Remote workspaces. One install provides encrypted remote access from your own devices and remote workspace switching inside the existing Harness UI.
7
+ `ds-harness-remote` is the Remote Host and workspace plugin for DSH. Harness keeps running on your work computer with its existing workspaces, tools, and permission controls; Remote gives your authorized devices another window into that environment.
8
8
 
9
- DeepSeek Remote 是 DSH Host + Remote 工作区插件。一次安装可提供本地设备到远端的加密远程访问能力,以及在现有 Harness UI 内切换远端工作区。
9
+ > Install this package with `dsh plugin`, not `npm install`. The DSH command updates the selected profile and adds the required bundle configuration.
10
10
 
11
- > Install this DSH bundle with `dsh plugin`, not with npm directly. The DSH command updates the
12
- > selected profile and adds the bundle configuration layer.
13
- >
14
- > 这是 DSH bundle,请通过 `dsh plugin` 安装,不要直接使用 npm。DSH 命令会更新指定 profile,
15
- > 并加入 bundle 配置层。
11
+ ## Highlights
16
12
 
17
- No Client mode switch is required. Choosing a remote workspace keeps the local UI unchanged, routes rc.2 through `RemoteHarnessApiProxy` or the alpha generation through the Typert Remote Gateway carrier, and falls back to the matching local transport on disconnect or exit.
13
+ - Continue active sessions and review progress from another computer, the web, or Android
14
+ - Send text and image prompts, answer questions, and handle permission requests
15
+ - Open workspaces on another authorized computer without replacing the native Harness interface
16
+ - Reach the Host without opening a public listening port or configuring router port forwarding
17
+ - Protect session traffic with authenticated end-to-end encryption
18
+ - Preview remote files through the optional, read-only `dsh-file-viewer` integration
19
+ - Open Host Codex projects in the existing Remote UI through the optional experimental Codex domain
18
20
 
19
- 无需启动或切换 Client 模式。选择远端工作区后,本地 UI 不变:rc.2 请求通过 `RemoteHarnessApiProxy` 路由,alpha 代际请求通过 Typert Remote Gateway carrier 路由;断线或退出时恢复对应的本地 transport。
21
+ ## Install
20
22
 
21
- ## User flow / 用户流程
23
+ ### DSH Desktop
22
24
 
23
- 1. Open **Remote** from the sidebar.
24
- 2. Sign in with GitHub or Zhihu QR authorization, or with your account and password. New password accounts can register through [Remote Web](https://dsh.r2049.cn/app/register); the site shows the current invitation requirements.
25
- 3. Enable remote control for the current computer, or select another online device to control it directly.
26
- 4. Enter an existing workspace or browse remote directories to open one. Use **Exit** to return local and stop forwarding.
25
+ [DSH Desktop](https://github.com/liguobao/dsh-desktop) includes Remote and enables it by default. No separate plugin installation is required.
27
26
 
28
- 1. 从侧边栏打开 **Remote** 入口。
29
- 2. 使用 GitHub/知乎扫码授权登录,或使用账号密码登录。新的账号密码用户可从 [Remote Web](https://dsh.r2049.cn/app/register) 注册,当前邀请要求以站点页面为准。
30
- 3. 为当前机器启用远端控制,或直接选择另一台在线设备并控制它。
31
- 4. 进入已有 Workspace,或浏览远端目录后打开 Workspace;使用 **退出** 回到本地并停止转发。
27
+ ### Existing DSH installation
32
28
 
33
- ## Harness images / Harness 图片
29
+ Add the exact package version to the `web` profile, then restart Harness:
34
30
 
35
- With DSH `dsh-v0.1.1-rc.2` or `dsh-v0.1.2-alpha.1`–`rc.1`, the native conversation UI
36
- can send images through `session.prompt` and render them through the read-only
37
- `session.attachment` lookup. Large ApiProxy or Gateway envelopes use bounded
38
- 512 KiB transfer chunks; image preprocessing, DeepSeek Files API upload, and
39
- file-id reuse stay on the Host in the official adapter.
40
-
41
- 使用 DSH `dsh-v0.1.1-rc.2` 或 `dsh-v0.1.2-alpha.1`–`rc.1` 时,原生会话 UI 可通过
42
- `session.prompt` 发送图片,并通过只读 `session.attachment` 回读显示。较大的 ApiProxy 或
43
- Gateway envelope 使用受限的 512 KiB 分块;图片预处理、DeepSeek Files API 上传与 file-id
44
- 复用仍由 Host 官方 adapter 完成。
45
-
46
- ## Directory browsing / 目录浏览
47
-
48
- - Calls remote `host.listDirectory` first.
49
- - Returns read-only metadata only: one-level children, absolute path, breadcrumbs, Home, hidden flag.
50
- - The picker does not read file contents and never writes or mutates directories.
51
-
52
- - 默认先调用远端 `host.listDirectory`。
53
- - 仅返回只读元数据:单层子目录、绝对路径、面包屑、Home、隐藏标记。
54
- - Workspace 选择器不读取文件内容,也不改文件系统、不建/改/删目录。
55
-
56
- ## Remote File Viewer / 远端文件查看
57
-
58
- Install `dsh-file-viewer` on the Host and Client profiles to reuse its in-app
59
- renderers for remote workspace files. Remote exposes only `stat`, bounded
60
- `readRange`, and directory `list` over the authenticated encrypted channel.
61
- Each transport read is capped at 512 KiB and larger viewer reads are assembled
62
- from multiple chunks. Provider root/locator authorization remains in File Viewer.
63
-
64
- 在 Host 与 Client profile 中安装 `dsh-file-viewer` 后,可复用其内置渲染器查看远端
65
- Workspace 文件。Remote 仅在认证加密通道中开放 `stat`、受限 `readRange` 与目录 `list`;
66
- 每个传输分块最多 512 KiB,更大的读取由客户端自动拼接。根目录与 locator 权限继续由
67
- File Viewer provider 校验。
68
-
69
- Remote preview never exposes write/delete/upload/execute, `openExternal`, or a
70
- general filesystem RPC.
31
+ ```sh
32
+ dsh plugin --profile web add ds-harness-remote@0.4.9
33
+ ```
71
34
 
72
- 远端预览不开放写入、删除、上传、执行、`openExternal` 或通用文件系统 RPC。
35
+ ### dsh-TUI Host
73
36
 
74
- ## Architecture / 架构
37
+ Remote can also run as a Host in a terminal-only [dsh-TUI](https://github.com/ccch1mneyyy/dsh-TUI) profile:
75
38
 
76
- ```text
77
- Local Harness UI
78
- -> ApiProxySwitch (rc.2) / TypertGatewaySwitch (alpha)
79
- -> RemoteHarnessApiProxy / RemoteTypertGateway
80
- -> Adaptive transport (LAN / P2P / TURN / Relay)
81
- -> Noise IK secure channel
82
- -> HarnessApiBridge / HarnessRemoteBridge allowlist
83
- -> Remote Harness ApiProxy or TypertGateway / FileViewerHost read-only bridge
39
+ ```sh
40
+ dsh plugin --profile dsh-tui add ds-harness-remote@0.4.9
84
41
  ```
85
42
 
43
+ After starting dsh-TUI, manage Remote with its native slash command:
44
+
86
45
  ```text
87
- 本地 Harness UI
88
- -> ApiProxySwitch(rc.2)/ TypertGatewaySwitch(alpha)
89
- -> RemoteHarnessApiProxy / RemoteTypertGateway
90
- -> 自适应传输(LAN / P2P / TURN / Relay)
91
- -> Noise IK 安全通道
92
- -> HarnessApiBridge / HarnessRemoteBridge 白名单
93
- -> 远端 Harness ApiProxy 或 TypertGateway / FileViewerHost 只读桥
46
+ /remote
47
+ /remote login
48
+ /remote login github
49
+ /remote status
50
+ /remote logout
94
51
  ```
95
52
 
96
- ## Key modules / 核心模块
97
-
98
- - `service.ts`: Host lifecycle / Host 生命周期
99
- - `client-runtime.ts`: device list, connection, workspace actions / 设备列表、连接、工作区操作
100
- - `client.ts`: settings UI, remote modal, sidebar, remote header / 设置卡片、Remote 弹窗、侧边栏、远端 Header
101
- - `harness-api-bridge.ts`: ApiProxy allowlist and stream bridge / ApiProxy 白名单与 stream 桥接
102
- - `harness-remote-bridge.ts`: v0.1.2 Typert Remote allowlist and stream carrier / v0.1.2 Typert Remote 白名单与 stream carrier
103
- - `file-viewer-bridge.ts`: bounded File Viewer read bridge / 受限 File Viewer 读取桥
104
- - `identity-store.ts` / `server-credentials.ts`: account-scoped identity and credentials / 账号隔离的身份与凭证
105
-
106
- ## Security model / 安全模型
107
-
108
- - The Harness Host does not require a public listening port. Clients can connect from anywhere with internet access over a bidirectional end-to-end encrypted channel.
109
- - Host/Client authenticate with long-lived X25519 keys using Noise IK.
110
- - ApiProxy methods and Typert Remote endpoints are allowlist-driven (fail-closed).
111
- - Workspace-picker browsing is metadata-only. Optional File Viewer preview is read-only, bounded, and provider-authorized.
112
- - Remote does not expose a direct shell, PTY, general tool RPC, remote desktop, or direct file-mutation API. Harness tools may still modify files or run commands under the Host's normal permission controls.
113
- - Data is stored under `$DSH_HOME/remote/servers/<origin-hash>/{host,client}`. Unix private keys must be `0600`.
53
+ `/remote login` uses Zhihu QR authorization by default; GitHub QR authorization is also supported.
114
54
 
115
- - Harness 主机无需开放公网监听端口;Client 可以从任意可上网的地方通过双向端到端加密链路连接。
116
- - Host/Client 使用长期 X25519 key + Noise IK 进行相互认证。
117
- - ApiProxy 方法与 Typert Remote endpoint 均使用固定白名单(未命中即拒绝)。
118
- - Workspace 选择器目录浏览仅返回元数据;可选的 File Viewer 预览只读、分块且继续执行 provider 授权。
119
- - Remote 不开放直接 Shell、PTY、通用工具 RPC、远程桌面或直接文件修改 API。Harness 工具仍可以在 Host 原有权限控制下修改文件或运行命令。
120
- - 数据位于 `$DSH_HOME/remote/servers/<origin-hash>/{host,client}`,私钥权限需 `0600`。
55
+ ## Quick start
121
56
 
122
- ## Configuration and install / 配置与安装
57
+ 1. Open **Remote** from the Harness sidebar.
58
+ 2. Sign in with GitHub or Zhihu QR authorization, or with your account and password.
59
+ 3. Enable remote control for the computer that will remain running.
60
+ 4. Sign in to the same account from DSH Desktop, [Remote Web](https://dsh.r2049.cn/app), or the [Android client](https://github.com/liguobao/ds-harness-remote/releases/latest).
61
+ 5. Select the online Host and open one of its workspaces.
123
62
 
124
- Settings are written to `$DSH_HOME/settings.yaml` under `ds-harness-remote` (restart required).
63
+ The public service currently uses the hosted Remote relay at `dsh.r2049.cn`. A supported self-hosted relay option is not available yet.
125
64
 
126
- 配置写入 `$DSH_HOME/settings.yaml` 的 `ds-harness-remote`(重启后生效)。
65
+ ## Experimental Codex workspaces
127
66
 
128
- ### dsh-TUI Host login / dsh-TUI Host 登录
67
+ Codex Remote is an optional domain inside this plugin. It lets authorized clients open projects reported by the Codex App Server while continuing to use the existing Harness or Android workspace and conversation UI.
129
68
 
130
- The Host runtime can start without the Desktop browser `connection` service. For a terminal-only
131
- [dsh-TUI](https://github.com/ccch1mneyyy/dsh-TUI) profile, install the plugin, start `dsh-tui`, and
132
- use the native `/remote` slash command:
69
+ Codex support is enabled by default and can be disabled in the DeepSeek Remote settings card. It remains experimental while long-running recovery and cross-version compatibility work continue. See the [Codex Remote technical notes](https://github.com/liguobao/ds-harness-remote/blob/main/docs/codex-remote.md) for the current boundaries and validation status.
133
70
 
134
- 纯终端 [dsh-TUI](https://github.com/ccch1mneyyy/dsh-TUI) profile 不需要 Desktop 浏览器的
135
- `connection` 服务。安装插件并启动 `dsh-tui` 后,使用原生 `/remote` Slash Command:
71
+ ## Security boundary
136
72
 
137
- ```sh
138
- dsh plugin --profile dsh-tui add ds-harness-remote@0.4.8
139
- ```
73
+ - Session traffic is encrypted on the Client and decrypted only by the selected Host using `Noise_IK_25519_ChaChaPoly_SHA256`.
74
+ - Account membership and the Host's locally pinned device identity must both authorize a connection.
75
+ - The Host creates outbound connections only; it does not listen on a public port.
76
+ - Remote does not expose a direct shell, PTY, general tool RPC, remote desktop, or file-mutation API. Harness tools continue to operate under the Host's normal permission controls.
77
+ - Workspace browsing returns bounded, read-only directory metadata. Optional file previews remain read-only and provider-authorized.
78
+ - The relay can observe necessary connection metadata but cannot read session messages, prompts, tool output, workspace paths, or file-preview content.
140
79
 
141
- ```text
142
- /remote
143
- /remote login [github|zhihu]
144
- /remote status
145
- /remote logout
146
- ```
80
+ ## Compatibility
147
81
 
148
- `/remote login` defaults to Zhihu and opens a TUI-native QR scene with a clickable authorization URL
149
- below the code. GitHub is also supported. dsh-TUI enables Host control by default; `/remote logout`
150
- revokes the Host and rotates its local identity. Host configuration is intentionally not exposed yet;
151
- the Server is `https://dsh.r2049.cn`.
152
-
153
- `/remote login` 默认使用知乎,并打开 TUI 原生二维码场景,二维码下方会显示可点击的授权 URL;
154
- 同时支持 GitHub。dsh-TUI 默认开启 Host 控制;`/remote logout` 会撤销 Host 并轮换本地身份。
155
- 目前不提供 Host 配置,Server 固定为 `https://dsh.r2049.cn`。
156
-
157
- Install the package through `dsh plugin`; installing it directly with npm does not update a DSH
158
- profile or add the bundle configuration layer. Use `/remote` for dsh-TUI Host authorization and
159
- status management. Server presence remains visible to authorized Remote clients while dsh-TUI is
160
- running.
161
-
162
- 请通过 `dsh plugin` 安装。直接使用 npm 安装不会更新 DSH profile,也不会加入 bundle 配置层。
163
- dsh-TUI Host 的授权与状态管理统一使用 `/remote`;dsh-TUI 运行时,Server presence 仍由已授权的
164
- Remote Client 查询。
165
-
166
- Codex Remote is an experimental optional domain inside this same plugin. It is enabled by default,
167
- can be disabled from the DeepSeek Remote settings card, and lets authorized clients open Host Codex
168
- projects in the existing Remote workspace and session UI. Desktop and Android can also browse to an
169
- existing Host directory, register it as a Codex project, and open the resulting virtual workspace. See
170
- [Codex Remote technical notes](../../docs/codex-remote.md) for workspace authority, data boundaries,
171
- configuration, and validation status.
172
-
173
- Codex Remote 是当前插件内部的实验性可选领域,默认开启,可在 DeepSeek Remote 设置卡片中关闭。它让
174
- 已授权客户端可以在现有 Remote Workspace 和 Session UI 中打开 Host 上的 Codex 项目。Desktop 与
175
- Android 还可浏览并选择 Host 上已存在的目录,将它注册为 Codex Project 后直接打开对应虚拟 Workspace。
176
- Workspace 来源、数据边界、配置方式和验证状态见
177
- [Codex Remote 技术说明](../../docs/codex-remote.md)。
82
+ Plugin `0.4.9` supports:
178
83
 
179
- ```sh
180
- export DSH_REMOTE_SERVER=https://dsh.r2049.cn
181
- ```
84
+ - DeepSeek Harness `dsh-v0.1.1-rc.2` through the official legacy `ApiProxy`
85
+ - DeepSeek Harness `dsh-v0.1.2-alpha.1` through `dsh-v0.1.2-rc.1` through the official Typert Remote Gateway
182
86
 
183
- Add the package to the `web` profile through DSH's plugin manager:
87
+ Both Desktop endpoints must use the same Harness transport generation. ApiProxy and Typert connections are not translated and mixed-generation connections are rejected.
184
88
 
185
- 通过 DSH 插件管理命令将包加入 `web` profile:
89
+ ## Documentation
186
90
 
187
- ```sh
188
- dsh plugin --profile web add ds-harness-remote@0.4.8
189
- ```
190
-
191
- Do not run npm directly; it does not configure the DSH profile.
192
-
193
- 不要直接运行 npm;它不会配置 DSH profile。
91
+ - [Complete English guide](https://github.com/liguobao/ds-harness-remote#readme)
92
+ - [完整中文说明](https://github.com/liguobao/ds-harness-remote/blob/main/README.zh.md)
93
+ - [dsh-TUI Remote guide](https://github.com/liguobao/ds-harness-remote/blob/main/docs/dsh-tui.md)
94
+ - [End-to-end encryption](https://github.com/liguobao/ds-harness-remote/blob/main/docs/end-to-end-encryption.md)
95
+ - [Network and transport](https://github.com/liguobao/ds-harness-remote/blob/main/docs/network.md)
96
+ - [Protocol reference](https://github.com/liguobao/ds-harness-remote/blob/main/docs/protocol.md)
194
97
 
195
- Alternatively, install the pinned GitHub release in DSH Desktop or with the CLI:
98
+ ## Project status
196
99
 
197
- 也可以在 DSH Desktop 中安装固定版本的 GitHub Release,或使用命令行安装:
198
-
199
- ```text
200
- github:liguobao/ds-harness-remote#v0.4.8
201
- ```
202
-
203
- ```sh
204
- dsh plugin --profile web add "github:liguobao/ds-harness-remote#v0.4.8"
205
- ```
100
+ This is an independent community project and is not an official DeepSeek product. DeepSeek and related names and marks belong to their respective owners.
206
101
 
207
- GitHub / 项目地址:<https://github.com/liguobao/ds-harness-remote>
102
+ Licensed under the [MIT License](https://github.com/liguobao/ds-harness-remote/blob/main/packages/plugin/LICENSE).
@@ -93,6 +93,8 @@ export declare class ClientModeRuntime {
93
93
  private readonly proxySwitch?;
94
94
  private readonly gatewaySwitch;
95
95
  private readonly codexStreams;
96
+ private connectionProgress?;
97
+ private connectionProgressRun;
96
98
  private closed;
97
99
  constructor(config: ResolvedConfig, identities: IdentityStore, server: ClientServerApi, apiProxy: ApiProxy | undefined, typertGateway: TypertGatewayLike, logger: SafeLogger, host?: HostAuthorizationControl | undefined, rtcFactoryProvider?: (options?: WeriftFactoryOptions) => Promise<RtcPeerConnectionFactory | undefined>);
98
100
  start(): Promise<void>;
@@ -131,6 +133,8 @@ export declare class ClientModeRuntime {
131
133
  private assertRemoteCompatible;
132
134
  private connect;
133
135
  private ensureConnected;
136
+ private updateConnectionProgress;
137
+ private clearConnectionProgress;
134
138
  handleControl(endpoint: string, payload: unknown, signal: AbortSignal): Promise<RpcResult<unknown>>;
135
139
  private requireIdentity;
136
140
  private authorizeHostPeer;
@@ -1 +1 @@
1
- {"version":3,"file":"client-runtime.d.ts","sourceRoot":"","sources":["../src/client-runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client-runtime.d.ts","sourceRoot":"","sources":["../src/client-runtime.ts"],"names":[],"mappings":"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"}
@@ -27,6 +27,8 @@ export class ClientModeRuntime {
27
27
  proxySwitch;
28
28
  gatewaySwitch;
29
29
  codexStreams = new Map();
30
+ connectionProgress;
31
+ connectionProgressRun = 0;
30
32
  closed = false;
31
33
  constructor(config, identities, server, apiProxy, typertGateway, logger, host, rtcFactoryProvider = loadNodeRtcFactory) {
32
34
  this.config = config;
@@ -68,7 +70,17 @@ export class ClientModeRuntime {
68
70
  ...targetStatus,
69
71
  connected: this.connected !== undefined,
70
72
  transport: this.connected?.client.getStats().mode ?? 'Disconnected',
73
+ connectedTargetDeviceId: this.connected?.target.deviceId,
71
74
  preferredTransports: this.config.forceRelay ? ['relay'] : ['lan', 'p2p', 'turn', 'relay'],
75
+ ...(this.connectionProgress === undefined ? {} : {
76
+ connectionProgress: {
77
+ targetDeviceId: this.connectionProgress.targetDeviceId,
78
+ phase: this.connectionProgress.phase,
79
+ ...(this.connectionProgress.activeTransports === undefined
80
+ ? {}
81
+ : { activeTransports: [...this.connectionProgress.activeTransports] }),
82
+ },
83
+ }),
72
84
  remoteFeatures: this.connected?.features ?? remoteHostFeatures(),
73
85
  ...(this.pendingWorkspaceSelection === undefined
74
86
  ? {}
@@ -144,6 +156,7 @@ export class ClientModeRuntime {
144
156
  async clearClientAuthorization() {
145
157
  const previous = this.connected;
146
158
  this.connected = undefined;
159
+ this.connectionProgress = undefined;
147
160
  this.pendingWorkspaceSelection = undefined;
148
161
  await this.closeCodexVirtual();
149
162
  this.proxySwitch?.selectLocal();
@@ -172,6 +185,7 @@ export class ClientModeRuntime {
172
185
  this.gatewaySwitch.selectLocal();
173
186
  const previous = this.connected;
174
187
  this.connected = undefined;
188
+ this.connectionProgress = undefined;
175
189
  this.pendingWorkspaceSelection = undefined;
176
190
  await this.closeCodexStreams(previous?.client);
177
191
  await previous?.client.close().catch(() => undefined);
@@ -186,11 +200,13 @@ export class ClientModeRuntime {
186
200
  this.assertRemoteCompatible(next);
187
201
  }
188
202
  catch (error) {
203
+ this.clearConnectionProgress(next.progressRunId);
189
204
  await next.client.close().catch(() => undefined);
190
205
  throw error;
191
206
  }
192
207
  const previous = this.connected;
193
208
  this.connected = next;
209
+ this.clearConnectionProgress(next.progressRunId);
194
210
  this.pendingWorkspaceSelection = undefined;
195
211
  await this.closeCodexVirtual();
196
212
  this.selectRemoteTarget(next);
@@ -340,6 +356,7 @@ export class ClientModeRuntime {
340
356
  await this.closeCodexStreams(this.connected?.client);
341
357
  await this.connected?.client.close().catch(() => undefined);
342
358
  this.connected = undefined;
359
+ this.connectionProgress = undefined;
343
360
  this.proxySwitch?.restore();
344
361
  this.gatewaySwitch.restore();
345
362
  }
@@ -541,61 +558,66 @@ export class ClientModeRuntime {
541
558
  }
542
559
  async connect(targetDeviceId, signal) {
543
560
  signal?.throwIfAborted();
561
+ const progressRunId = this.connectionProgressRun + 1;
562
+ this.connectionProgressRun = progressRunId;
563
+ this.connectionProgress = { runId: progressRunId, targetDeviceId, phase: 'checking-host' };
544
564
  const identity = this.requireIdentity();
545
- const serverDevice = (await this.server.listDevices()).find(device => device.deviceId === targetDeviceId);
546
- if (serverDevice === undefined) {
547
- throw new ClientModeError('MEMBERSHIP_REQUIRED', 'The selected Host is not authorized for this account.');
548
- }
549
- const target = await this.authorizeHostPeer(serverDevice);
550
- const presence = await this.server.presenceFor(targetDeviceId);
551
- if (!presence.online)
552
- throw new ClientModeError('HOST_OFFLINE', 'The selected Host is offline.', true);
553
- const credentials = await this.server.authenticate(identity);
554
- const rtcFactory = this.config.forceRelay
555
- ? undefined
556
- : await this.rtcFactoryProvider({ routeTargets: [this.server.baseUrl] }).catch(() => undefined);
557
- if (!this.config.forceRelay && rtcFactory === undefined) {
558
- this.logger.warn('remote Harness WebRTC backend unavailable; using relay', {
559
- targetDeviceId: shortId(target.deviceId),
560
- });
561
- }
562
- let webRtcFallback = false;
563
- const createTransport = (attempt) => new AdaptiveTransport(websocketUrl(this.server.baseUrl), {
564
- role: 'client',
565
- deviceId: identity.deviceId,
566
- accessToken: credentials.accessToken,
567
- targetDeviceId,
568
- forceRelay: this.config.forceRelay || attempt === 'relay',
569
- preferredTransports: preferredTransportsForAttempt(attempt),
570
- negotiateTimeoutMs: attempt === 'direct' ? DIRECT_WEBRTC_NEGOTIATE_TIMEOUT_MS : undefined,
571
- ...(rtcFactory === undefined || attempt === 'relay' ? {} : { rtcFactory }),
572
- fetchIceServers: async (connectionId) => iceServersForAttempt(attempt, await this.server.turnCredentials(connectionId)),
573
- onWebRtcFallback: (error, diagnostics) => {
574
- webRtcFallback = true;
575
- this.logger.warn(attempt === 'direct'
576
- ? 'remote Harness direct WebRTC failed; trying TURN'
577
- : 'remote Harness TURN WebRTC failed; using relay', {
565
+ let client;
566
+ try {
567
+ const serverDevice = (await this.server.listDevices()).find(device => device.deviceId === targetDeviceId);
568
+ if (serverDevice === undefined) {
569
+ throw new ClientModeError('MEMBERSHIP_REQUIRED', 'The selected Host is not authorized for this account.');
570
+ }
571
+ this.updateConnectionProgress(progressRunId, 'authorizing-peer');
572
+ const target = await this.authorizeHostPeer(serverDevice);
573
+ const presence = await this.server.presenceFor(targetDeviceId);
574
+ if (!presence.online)
575
+ throw new ClientModeError('HOST_OFFLINE', 'The selected Host is offline.', true);
576
+ const credentials = await this.server.authenticate(identity);
577
+ const rtcFactory = this.config.forceRelay
578
+ ? undefined
579
+ : await this.rtcFactoryProvider({ routeTargets: [this.server.baseUrl] }).catch(() => undefined);
580
+ if (!this.config.forceRelay && rtcFactory === undefined) {
581
+ this.logger.warn('remote Harness WebRTC backend unavailable; using relay', {
578
582
  targetDeviceId: shortId(target.deviceId),
579
- attempt,
580
- reason: diagnosticReason(error),
581
583
  });
582
- if (diagnostics !== undefined) {
583
- this.logger.debug('remote Harness WebRTC fallback diagnostics', {
584
+ }
585
+ let webRtcFallback = false;
586
+ const createTransport = (attempt) => new AdaptiveTransport(websocketUrl(this.server.baseUrl), {
587
+ role: 'client',
588
+ deviceId: identity.deviceId,
589
+ accessToken: credentials.accessToken,
590
+ targetDeviceId,
591
+ forceRelay: this.config.forceRelay || attempt === 'relay',
592
+ preferredTransports: preferredTransportsForAttempt(attempt),
593
+ negotiateTimeoutMs: attempt === 'direct' ? DIRECT_WEBRTC_NEGOTIATE_TIMEOUT_MS : undefined,
594
+ ...(rtcFactory === undefined || attempt === 'relay' ? {} : { rtcFactory }),
595
+ fetchIceServers: async (connectionId) => iceServersForAttempt(attempt, await this.server.turnCredentials(connectionId)),
596
+ onWebRtcFallback: (error, diagnostics) => {
597
+ webRtcFallback = true;
598
+ this.logger.warn(attempt === 'direct'
599
+ ? 'remote Harness direct WebRTC failed; trying TURN'
600
+ : 'remote Harness TURN WebRTC failed; using relay', {
584
601
  targetDeviceId: shortId(target.deviceId),
585
602
  attempt,
586
- ...webrtcDiagnosticsLogFields(diagnostics),
603
+ reason: diagnosticReason(error),
587
604
  });
588
- }
589
- },
590
- });
591
- const attempts = this.config.forceRelay || rtcFactory === undefined
592
- ? ['relay']
593
- : ['direct', 'turn', 'relay'];
594
- let transport;
595
- let client;
596
- try {
605
+ if (diagnostics !== undefined) {
606
+ this.logger.debug('remote Harness WebRTC fallback diagnostics', {
607
+ targetDeviceId: shortId(target.deviceId),
608
+ attempt,
609
+ ...webrtcDiagnosticsLogFields(diagnostics),
610
+ });
611
+ }
612
+ },
613
+ });
614
+ const attempts = this.config.forceRelay || rtcFactory === undefined
615
+ ? ['relay']
616
+ : ['direct', 'turn', 'relay'];
617
+ let transport;
597
618
  for (const attempt of attempts) {
598
619
  webRtcFallback = false;
620
+ this.updateConnectionProgress(progressRunId, 'probing', activeTransportsForAttempt(attempt));
599
621
  transport = createTransport(attempt);
600
622
  client = new RemoteClientCore(new ClientSecureTransport(transport, identity, target), 60_000);
601
623
  await client.connect();
@@ -616,10 +638,13 @@ export class ClientModeRuntime {
616
638
  }
617
639
  const connectedClient = client;
618
640
  const connectedTransport = transport;
641
+ const connectedPreference = transportPreferenceForMode(connectedClient.getStats().mode);
642
+ this.updateConnectionProgress(progressRunId, 'connected', connectedPreference === undefined ? undefined : [connectedPreference]);
619
643
  connectedClient.onClose(() => {
620
644
  if (this.connected?.client !== connectedClient)
621
645
  return;
622
646
  this.connected = undefined;
647
+ this.connectionProgress = undefined;
623
648
  this.pendingWorkspaceSelection = undefined;
624
649
  void this.closeCodexVirtual();
625
650
  this.proxySwitch?.selectLocal();
@@ -651,10 +676,12 @@ export class ClientModeRuntime {
651
676
  target,
652
677
  transport: connectedTransport,
653
678
  features,
679
+ progressRunId,
654
680
  ...(serverDevice.clientVersion === undefined ? {} : { clientVersion: serverDevice.clientVersion }),
655
681
  };
656
682
  }
657
683
  catch (error) {
684
+ this.clearConnectionProgress(progressRunId);
658
685
  await client?.close().catch(() => undefined);
659
686
  throw error;
660
687
  }
@@ -665,9 +692,24 @@ export class ClientModeRuntime {
665
692
  const next = await this.connect(targetDeviceId, signal);
666
693
  const previous = this.connected;
667
694
  this.connected = next;
695
+ this.clearConnectionProgress(next.progressRunId);
668
696
  await previous?.client.close().catch(() => undefined);
669
697
  return next;
670
698
  }
699
+ updateConnectionProgress(runId, phase, activeTransports) {
700
+ if (this.connectionProgress?.runId !== runId)
701
+ return;
702
+ this.connectionProgress = {
703
+ runId,
704
+ targetDeviceId: this.connectionProgress.targetDeviceId,
705
+ phase,
706
+ ...(activeTransports === undefined ? {} : { activeTransports }),
707
+ };
708
+ }
709
+ clearConnectionProgress(runId) {
710
+ if (this.connectionProgress?.runId === runId)
711
+ this.connectionProgress = undefined;
712
+ }
671
713
  async handleControl(endpoint, payload, signal) {
672
714
  try {
673
715
  if (endpoint === 'status')
@@ -1077,6 +1119,22 @@ function preferredTransportsForAttempt(attempt) {
1077
1119
  return ['turn', 'relay'];
1078
1120
  return ['relay'];
1079
1121
  }
1122
+ function activeTransportsForAttempt(attempt) {
1123
+ if (attempt === 'direct')
1124
+ return ['lan', 'p2p'];
1125
+ return [attempt];
1126
+ }
1127
+ function transportPreferenceForMode(mode) {
1128
+ if (mode === 'LAN')
1129
+ return 'lan';
1130
+ if (mode === 'P2P')
1131
+ return 'p2p';
1132
+ if (mode === 'TURN')
1133
+ return 'turn';
1134
+ if (mode === 'Relay')
1135
+ return 'relay';
1136
+ return undefined;
1137
+ }
1080
1138
  function iceServersForAttempt(attempt, iceServers) {
1081
1139
  return attempt === 'direct' ? stunOnlyIceServers(iceServers) : iceServers;
1082
1140
  }