@deepseek-ai/dsh-host-directory-picker-native 0.1.1-rc.2 → 0.1.2-alpha.2

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.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/host/directory-picker-native/README.md
5
- README.md: 4dd0c79d080fe097d063cfd2200e30aafc2d2d42
6
- README.zh.md: 36cfe85d22a5c5e598bbda6ffbf8823b44479b8d
5
+ README.md: 420ec71899366733c9be538ca5900644e81b668d
6
+ README.zh.md: a6671d2f93fbc605bfa74d73e80d835cb0c7824e
package/README.md CHANGED
@@ -1,14 +1,89 @@
1
+ ---
2
+ description: "Native-OS-chooser backend of the directory-picker seam: opens one platform chooser per pick for operators sitting at the web GUI host's display."
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-host-directory-picker-native
2
7
 
3
8
  English | [中文](README.zh.md)
4
9
 
5
- The **native-OS-chooser backend** of the [directory-picker seam](../directory-picker/README.md): `NativeDirectoryPicker` registers `ctx.directoryPicker` with the `native` capability, whose `pick(signal)` opens one native chooser per call and resolves the chosen absolute path (`null` on cancel). Platform tools run without a shell: `osascript` on macOS and Zenity with a KDialog fallback on Linux; the caller's abort terminates the native process. Windows opens the modern `IFileOpenDialog` in a spawned child process — a koffi-driven COM conversation on the child's main thread with the best thread DPI awareness the host accepts (per-monitor-v2 first), aborted by posting `WM_CLOSE` to the dialog thread. Only viable when the operator sits at the host's display — remote deployments compose [`-browse`](../directory-picker-browse/README.md) instead. The command boundary (`DirectoryPickerRunner`) and platform facts are injectable. The shared no-shell subprocess runner lives in [`dsh-native-command`](../../util/native-command/README.md).
10
+ ## Summary
11
+
12
+ An operator at the host's display picks a workspace directory through a native OS chooser: `dsh-host-directory-picker-native` opens one platform directory chooser per pick and resolves the chosen absolute path (`null` on cancel). macOS drives `osascript`, Linux uses Zenity with a KDialog fallback, and Windows opens the modern `IFileOpenDialog` in a spawned child process. Only viable when the operator sits at the host's display — remote deployments compose the [browse backend](../directory-picker-browse/README.md) instead. One composition row also registers the matching browser-side interaction in the workspace flow, so it selects both sides.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ Compose this backend when the operator works at the host's display and a native chooser is the right interaction. A workspace flow that opens a directory picker calls `pick(signal)` once per open request; the returned promise resolves with the chosen absolute path, or `null` when the operator cancels.
29
+
30
+ ### When to choose it
31
+
32
+ Choose this backend for a workstation-local operator on macOS, Windows, or desktop Linux. Choose the [browse backend](../directory-picker-browse/README.md) when clients cannot reach an OS chooser — remote browsers, SSH-forwarded sessions, or unattended hosts. When the situation varies, the [adaptive chooser](../directory-picker-auto/README.md) resolves it at boot.
33
+
34
+ ### What an operator experiences
35
+
36
+ Each call opens one native chooser on the host display and waits for the operator; aborting the caller's signal terminates the chooser process instead of leaving it open. On Linux the chooser needs either Zenity or KDialog installed; with neither present, `pick` rejects with an actionable error instead of falling back to a typed-path prompt. The browser half of this package registers a renderless flow occupant into the workspace flow — every `open` request drives `directoryPicker/pick` and reports the one outcome (picked path, cancel, or failure).
37
+
38
+ ### Observable failures
39
+
40
+ A cancel returns `null`, not an error. Missing platform tooling, a failed chooser launch, or an aborted pick surfaces as a rejection the UI can present; the [browse backend](../directory-picker-browse/README.md) remains the composition-level fallback for deployments where native picking is unreliable.
41
+
42
+ -----
43
+
44
+ <a id="understand-the-implementation"></a>
45
+ ## Understand the implementation
46
+
47
+ <details>
48
+ <summary>Implementation internals — click to expand</summary>
49
+
50
+ ### Design concept
6
51
 
7
- **Dual-face package**: the browser half (`./client`) registers a renderless flow occupant into [ui-workspace's](../../client/ui-workspace/README.md) two directory-flow holes — each `open` request drives `host.pickDirectory` and reports the one outcome (picked path / cancel / failure) through the hole's owner conversation. Both directory-flow declarations must be live before either contribution installs. One cordis.yml row therefore composes both sides of the native interaction; the client carries no capability-kind branching, and mounting a second flow package fails at load (the holes are `single` kind).
52
+ The backend is a thin service over a platform chooser: `NativeDirectoryPicker` registers the `native` capability whose `pick` forwards to `pickNativeDirectory`, and the chooser runs as a subprocess so the host process never blocks on the dialog. The command boundary (`DirectoryPickerRunner`) and platform facts are injectable, and the shared no-shell subprocess runner lives in [`dsh-native-command`](../../util/native-command/README.md).
8
53
 
54
+ ### Platform mechanics
55
+
56
+ Platform tools run without a shell: `osascript` on macOS, and Zenity with a KDialog fallback on Linux; the caller's abort terminates the native process. Windows opens the modern `IFileOpenDialog` in a spawned child process — a koffi-driven COM conversation on the child's main thread with the best thread DPI awareness the host accepts (per-monitor-v2 first), aborted by posting `WM_CLOSE` to the dialog thread.
57
+
58
+ ### Source map
59
+
60
+ | File | Role |
61
+ |---|---|
62
+ | [`src/index.ts`](src/index.ts) | Plugin entry: `NativeDirectoryPicker` service with the stable `native` capability |
63
+ | [`src/native-picker.ts`](src/native-picker.ts) | Chooser dispatch: platform selection, subprocess running, abort wiring |
64
+ | [`src/win32-dialog.ts`](src/win32-dialog.ts) + siblings | Windows child-process `IFileOpenDialog` via koffi, DPI handling, `WM_CLOSE` abort |
65
+
66
+ </details>
67
+
68
+ -----
69
+
70
+ <a id="further-exploration"></a>
71
+ ## Further Exploration
72
+
73
+ Read these when the backend contract is not enough: the seam definition first, then the alternative backend and the chooser that selects between them.
74
+
75
+ - [Directory-picker seam](../directory-picker/README.md) — the `native` capability contract and the typed error vocabulary.
76
+ - [Directory-picker capability seam decision](../../../.agents/notes/implemented/architecture/2026-07-28-directory-picker-capability-seam.md) — why backends differ in interaction shape.
77
+ - [Browse backend](../directory-picker-browse/README.md) — the in-app alternative for remote clients.
78
+ - [Adaptive chooser](../directory-picker-auto/README.md) — boot-time resolution between native and browse.
79
+ - [No-shell subprocess runner](../../util/native-command/README.md) — the shared subprocess primitive the chooser runs on.
80
+
81
+ -----
82
+
83
+ <a id="model-experience"></a>
9
84
  ## Model Experience
10
85
 
11
- None, as the backend serves the GUI host's directory selection; nothing here reaches a model request.
86
+ None, as the GUI-host picking backend registers nothing model-facing.
12
87
 
13
88
  #### KV Cache effect
14
89
 
@@ -16,5 +91,20 @@ None; this package neither assembles nor sends a provider request.
16
91
 
17
92
  ## Known Limitations and Deferred Work
18
93
 
94
+ <a id="known-limitations-and-deferred-work"></a>
95
+
96
+
97
+ These limits define when the native interaction is unavailable or fragile. They are current package constraints, not a task backlog.
98
+
19
99
  - **Linux requires desktop tooling** — with neither Zenity nor KDialog installed, `pick` rejects with an actionable error; it does not fall back to a typed-path prompt (the browse backend is that fallback at the composition level).
20
- - **Windows has no mechanism fallback** — the child-process picker through packaged koffi is the only native tier, so a COM refusal or dialog crash surfaces the failure. The browse backend remains the fallback at the composition level.
100
+ - **Windows has no mechanism fallback** — the child-process picker through packaged koffi is the only native tier, so a COM refusal or dialog crash surfaces the failure; the browse backend remains the fallback at the composition level.
101
+
102
+ <a id="dev-note"></a>
103
+ ### Dev Note
104
+
105
+ <details>
106
+ <summary>Working context for maintainers — click to expand</summary>
107
+
108
+ None.
109
+
110
+ </details>
package/README.zh.md CHANGED
@@ -1,14 +1,89 @@
1
+ ---
2
+ description: "目录选择 seam 的原生 OS 选择器后端:为坐在 web GUI 宿主屏幕前的操作者每次打开一个平台选择器。"
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-host-directory-picker-native
2
7
 
3
8
  [English](README.md) | 中文
4
9
 
5
- [目录选择 seam](../directory-picker/README.zh.md) 的**原生 OS 选择器后端**:`NativeDirectoryPicker` 以 `native` 能力注册 `ctx.directoryPicker`,其 `pick(signal)` 每次调用打开一个原生选择器并解析出所选绝对路径(取消时为 `null`)。平台工具不经 shell 调用:macOS 使用 `osascript`,Linux 使用 Zenity 并以 KDialog 回退;调用方的中止信号会终止原生进程。Windows 在 spawn 的子进程中打开现代 `IFileOpenDialog`——由 koffi 在子进程主线程上驱动的 COM 会话,采用宿主接受的最佳线程 DPI 感知(优先 per-monitor-v2),中止时向对话框线程投递 `WM_CLOSE`。只有操作者坐在宿主屏幕前时才可用——远程部署应组合 [`-browse`](../directory-picker-browse/README.zh.md)。命令边界(`DirectoryPickerRunner`)与平台事实可注入。共享的免 shell 子进程运行器位于 [`dsh-native-command`](../../util/native-command/README.zh.md)。
10
+ ## 概述
11
+
12
+ 坐在宿主屏幕前的操作者通过原生 OS 选择器选择工作区目录:`dsh-host-directory-picker-native` 每次选择打开一个平台目录选择器,并解析出所选绝对路径(取消时为 `null`)。macOS 驱动 `osascript`,Linux 使用 Zenity 并以 KDialog 回退,Windows 在 spawn 的子进程中打开现代 `IFileOpenDialog`。只有操作者坐在宿主屏幕前时才可用——远程部署应组合[浏览后端](../directory-picker-browse/README.zh.md)。一行组合配置还会在工作区流程中注册匹配的浏览器侧交互,因此同时选择两侧。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ 当操作者工作在宿主屏幕前、且原生选择器是合适的交互时,组合此后端。打开目录选择器的工作区流程每次 open 请求调用一次 `pick(signal)`;返回的 promise 解析为所选绝对路径,操作者取消时解析为 `null`。
29
+
30
+ ### 何时选择
31
+
32
+ 为 macOS、Windows 或桌面 Linux 上的工作站本地操作者选择此后端。当客户端无法触达 OS 选择器时——远程浏览器、SSH 转发会话或无人值守宿主——请选择[浏览后端](../directory-picker-browse/README.zh.md)。处境因启动而异时,[自适应选择器](../directory-picker-auto/README.zh.md)在启动时判定。
33
+
34
+ ### 操作者会看到什么
35
+
36
+ 每次调用在宿主屏幕上打开一个原生选择器并等待操作者;中止调用方的信号会终止选择器进程,而不是让它留在屏幕上。Linux 上选择器需要安装 Zenity 或 KDialog 之一;两者都没有时,`pick` 以包含解决建议的错误拒绝,而不会回退为手输路径提示。本包的 browser 半侧向工作区流程注册一个无渲染的流程占用者——每次 `open` 请求驱动 `directoryPicker/pick`,并上报唯一结果(所选路径、取消或失败)。
37
+
38
+ ### 可观察的失败
39
+
40
+ 取消返回 `null`,不是错误。平台工具缺失、选择器启动失败或 pick 被中止都会以界面可以呈现的拒绝形式浮现;[浏览后端](../directory-picker-browse/README.zh.md)仍是原生选择不可靠的部署在组合层面的回退。
41
+
42
+ -----
43
+
44
+ <a id="understand-the-implementation"></a>
45
+ ## 理解实现
46
+
47
+ <details>
48
+ <summary>实现细节——点击展开</summary>
49
+
50
+ ### 设计理念
6
51
 
7
- **双面包**:浏览器端(`./client`)向 [ui-workspace](../../client/ui-workspace/README.zh.md) 的两个目录流 slot 注册一个无渲染的流程占用者——每次 `open` 请求驱动 `host.pickDirectory`,并通过 slot 的属主交互约定上报唯一结果(所选路径/取消/失败)。两个目录流程声明必须同时处于有效状态,任一贡献才会安装。因此一行 cordis.yml 同时组合原生交互的两侧;客户端不包含任何按能力类型进行的分支,挂载第二个流程包会在加载期失败(slot 的 kind 为 `single`)。
52
+ 后端是平台选择器之上的一层薄服务:`NativeDirectoryPicker` 注册 `native` 能力,其 `pick` 转发给 `pickNativeDirectory`,选择器以子进程运行,宿主进程因此绝不为对话框阻塞。命令边界(`DirectoryPickerRunner`)与平台事实可注入;共享的免 shell 子进程运行器位于 [`dsh-native-command`](../../util/native-command/README.zh.md)
8
53
 
54
+ ### 平台机制
55
+
56
+ 平台工具不经 shell 调用:macOS 使用 `osascript`,Linux 使用 Zenity 并以 KDialog 回退;调用方的中止信号会终止原生进程。Windows 在 spawn 的子进程中打开现代 `IFileOpenDialog`——由 koffi 在子进程主线程上驱动的 COM 会话,采用宿主接受的最佳线程 DPI 感知(优先 per-monitor-v2),中止时向对话框线程投递 `WM_CLOSE`。
57
+
58
+ ### 源码地图
59
+
60
+ | 文件 | 职责 |
61
+ |---|---|
62
+ | [`src/index.ts`](src/index.ts) | 插件入口:持有稳定 `native` 能力的 `NativeDirectoryPicker` 服务 |
63
+ | [`src/native-picker.ts`](src/native-picker.ts) | 选择器分发:平台选择、子进程运行、中止接线 |
64
+ | [`src/win32-dialog.ts`](src/win32-dialog.ts) 及同族文件 | Windows 经 koffi 的子进程 `IFileOpenDialog`、DPI 处理、`WM_CLOSE` 中止 |
65
+
66
+ </details>
67
+
68
+ -----
69
+
70
+ <a id="further-exploration"></a>
71
+ ## 进一步探索
72
+
73
+ 当后端约定不够用时阅读以下内容:先看 seam 定义,再看替代后端与在两者之间选择的那个选择器。
74
+
75
+ - [目录选择 seam](../directory-picker/README.zh.md)——`native` 能力约定与类型化错误词汇。
76
+ - [目录选择能力 seam 决策](../../../.agents/notes/implemented/architecture/2026-07-28-directory-picker-capability-seam.zh.md)——后端为何在交互形态上彼此不同。
77
+ - [浏览后端](../directory-picker-browse/README.zh.md)——面向远程客户端的应用内替代方案。
78
+ - [自适应选择器](../directory-picker-auto/README.zh.md)——native 与 browse 之间的启动时判定。
79
+ - [免 shell 子进程运行器](../../util/native-command/README.zh.md)——选择器运行所依赖的共享子进程原语。
80
+
81
+ -----
82
+
83
+ <a id="model-experience"></a>
9
84
  ## 模型体验
10
85
 
11
- 无。该后端服务于 GUI 宿主的目录选择;这里没有任何内容进入模型请求。
86
+ 无。GUI 宿主的目录选择后端不注册任何面向模型的内容。
12
87
 
13
88
  #### KV Cache 影响
14
89
 
@@ -16,5 +91,20 @@
16
91
 
17
92
  ## 已知限制与延期工作
18
93
 
19
- - **Linux 依赖桌面工具**——Zenity 与 KDialog 均未安装时,`pick` 以包含解决建议的错误拒绝;它不会回退为手输路径提示(组合层面的回退是 browse 后端)。
20
- - **Windows 没有机制级回退**——通过打包依赖 koffi 运行的子进程选择器是唯一原生层级,因此 COM 拒绝或对话框崩溃会直接上报失败。组合层面的回退仍是 browse 后端。
94
+ <a id="known-limitations-and-deferred-work"></a>
95
+
96
+
97
+ 这些限制说明原生交互何时不可用或易碎。它们是当前包约束,不是任务积压。
98
+
99
+ - **Linux 依赖桌面工具**——Zenity 与 KDialog 均未安装时,`pick` 以包含解决建议的错误拒绝;它不会回退为手输路径提示(组合层面的回退是浏览后端)。
100
+ - **Windows 没有机制级回退**——通过打包依赖 koffi 运行的子进程选择器是唯一原生层级,因此 COM 拒绝或对话框崩溃会直接上报失败;组合层面的回退仍是浏览后端。
101
+
102
+ <a id="dev-note"></a>
103
+ ### 开发备注
104
+
105
+ <details>
106
+ <summary>维护者的工作上下文——点击展开</summary>
107
+
108
+ 无。
109
+
110
+ </details>
package/lib/worker.cjs CHANGED
@@ -21,7 +21,7 @@
21
21
  function readUtf16(koffi, address) {
22
22
  const bytes = Buffer.from(koffi.view(address, 32768));
23
23
  let end = 0;
24
- while (end + 1 < bytes.length && bytes[end] !== 0) end += 2;
24
+ while (end + 1 < bytes.length && !(bytes[end] === 0 && bytes[end + 1] === 0)) end += 2;
25
25
  return bytes.toString("utf16le", 0, end);
26
26
  }
27
27
  const COINIT_APARTMENTTHREADED = 2;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-host-directory-picker-native",
3
3
  "description": "Native-OS-chooser backend of the directory-picker seam for the DeepSeek Harness web GUI host",
4
- "version": "0.1.1-rc.2",
4
+ "version": "0.1.2-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -38,16 +38,16 @@
38
38
  "license": "MIT",
39
39
  "dependencies": {
40
40
  "koffi": "^3.1.0",
41
- "@deepseek-ai/dsh-host-directory-picker": "^0.1.1-rc.2",
42
- "@deepseek-ai/dsh-native-command": "^0.1.1-rc.2"
41
+ "@deepseek-ai/dsh-host-directory-picker": "^0.1.2-alpha.2",
42
+ "@deepseek-ai/dsh-native-command": "^0.1.2-alpha.2"
43
43
  },
44
44
  "peerDependencies": {
45
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
46
- "@deepseek-ai/cordis": "^4.0.1"
45
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
46
+ "@deepseek-ai/cordis": "^4.0.2"
47
47
  },
48
48
  "devDependencies": {
49
49
  "tsx": "^4.19.2",
50
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
51
- "@deepseek-ai/cordis": "^4.0.1"
50
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
51
+ "@deepseek-ai/cordis": "^4.0.2"
52
52
  }
53
53
  }