@x1a0f3n9/dsh-host-directory-picker-auto 0.1.5-rc.3
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +122 -0
- package/README.zh.md +122 -0
- package/lib/index.js +143 -0
- package/lib/types/index.d.ts +45 -0
- package/lib/types/probe.d.ts +21 -0
- package/lib/types/resolve.d.ts +40 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/host/directory-picker-auto/README.md
|
|
5
|
+
README.md: a36d7e115f78089b85c79ae8036d7199df74b821
|
|
6
|
+
README.zh.md: 283f8c5bcbfabfa4fdeafc73283ff140ac1697fe
|
package/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Adaptive chooser of the directory-picker seam: resolves the web GUI host's situation once at boot and mounts the matching native or browse backend."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @x1a0f3n9/dsh-host-directory-picker-auto
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
`dsh-host-directory-picker-auto` picks the right directory-picking interaction for every boot: it resolves the host's situation once at boot and mounts the matching backend — [native](../directory-picker-native/README.md) or [browse](../directory-picker-browse/README.md) — together with its browser half, as real Loader entries in the in-memory root tree. The resolution is one pure boot-time sample: `native` requires a loopback-only bind, a non-SSH launch, and a servable display session; anything ambiguous resolves to `browse`, which works everywhere. Pinning an interaction means composing that backend directly. The mounted capability stays stable for the service lifetime, as the seam requires.
|
|
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 plugin instead of a concrete backend when the same composition must serve hosts that differ: local workstation sessions where a native chooser works, and remote or headless sessions where only the in-app browser works. The chooser inspects the host once at boot and mounts the matching interaction.
|
|
29
|
+
|
|
30
|
+
### How the choice is made
|
|
31
|
+
|
|
32
|
+
`native` requires every signal that the operator can see the host display and the native backend can serve it: a loopback-only bind (read from the injected `webServer`; an all-interfaces bind admits remote browsers no OS chooser can reach), no SSH launch (the shared [launch-environment](../../util/launch-environment/README.md) predicate ignores project/user `.env` values and checks only inherited non-empty `SSH_CONNECTION`/`SSH_TTY`), and a servable display session — assumed on darwin and win32; on linux, `DISPLAY`/`WAYLAND_DISPLAY` plus a zenity or kdialog binary on `PATH`; never on any other platform. Anything ambiguous resolves to `browse`, which works everywhere.
|
|
33
|
+
|
|
34
|
+
### What you get
|
|
35
|
+
|
|
36
|
+
The resolved interaction arrives as an ordinary Loader entry: the backend registers `ctx.directoryPicker`, and its browser half is discovered by the client module table exactly as a config row's would be, so the seam's one-row-swaps-both-faces invariant holds. Unloading the chooser removes the entry, unloading both faces with it. The sample happens exactly once per boot, so the mounted capability stays stable for the service lifetime.
|
|
37
|
+
|
|
38
|
+
### Pinning an interaction
|
|
39
|
+
|
|
40
|
+
Pinning is not a config field here: compose the `-native` or `-browse` row directly instead of this one — that is the seam's documented swap point. Mounting the chooser and a backend row together fails loud (duplicate `directoryPicker` service, duplicate client flow in the `single` holes).
|
|
41
|
+
|
|
42
|
+
### Observable failures
|
|
43
|
+
|
|
44
|
+
A wrong `native` choice degrades to the backend's existing retryable failure dialog rather than a broken composition; composing `-browse` directly selects the safe interaction for deployments whose situation the probe cannot prove.
|
|
45
|
+
|
|
46
|
+
-----
|
|
47
|
+
|
|
48
|
+
<a id="understand-the-implementation"></a>
|
|
49
|
+
## Understand the implementation
|
|
50
|
+
|
|
51
|
+
<details>
|
|
52
|
+
<summary>Implementation internals — click to expand</summary>
|
|
53
|
+
|
|
54
|
+
### Design concept
|
|
55
|
+
|
|
56
|
+
The chooser is a pure decision plus a mount: `resolveDirectoryPickerBackend` samples host facts once at boot and returns a backend kind, and `apply` mounts the matching backend and surface packages as real Loader entries in the in-memory root tree — never persisted to a config file, because the root tree's `write()` is a no-op. The effect's disposer removes both entries and joins their fibers' teardown, so unloading returns only after both faces of the mounted interaction quiesced.
|
|
57
|
+
|
|
58
|
+
### The resolution table
|
|
59
|
+
|
|
60
|
+
| Condition | Backend |
|
|
61
|
+
|---|---|
|
|
62
|
+
| Bind host is not `127.0.0.1` | `browse` |
|
|
63
|
+
| `SSH_CONNECTION` or `SSH_TTY` present | `browse` |
|
|
64
|
+
| darwin or win32 | `native` |
|
|
65
|
+
| linux with a chooser binary and a display | `native` |
|
|
66
|
+
| anything else | `browse` |
|
|
67
|
+
|
|
68
|
+
### Source map
|
|
69
|
+
|
|
70
|
+
| File | Role |
|
|
71
|
+
|---|---|
|
|
72
|
+
| [`src/index.ts`](src/index.ts) | Plugin entry: `BACKEND_PACKAGES`/`SURFACE_PACKAGES` maps, `apply` mount and unmount |
|
|
73
|
+
| [`src/resolve.ts`](src/resolve.ts) | `resolveDirectoryPickerBackend` — the pure boot-time decision |
|
|
74
|
+
| [`src/probe.ts`](src/probe.ts) | Host probes: `hasLinuxChooserBinary`, `canExecute` |
|
|
75
|
+
|
|
76
|
+
</details>
|
|
77
|
+
|
|
78
|
+
-----
|
|
79
|
+
|
|
80
|
+
<a id="further-exploration"></a>
|
|
81
|
+
## Further Exploration
|
|
82
|
+
|
|
83
|
+
Read these when the chooser's contract is not enough: the seam definition first, then the two backends it mounts.
|
|
84
|
+
|
|
85
|
+
- [Directory-picker seam](../directory-picker/README.md) — the capability contract the chooser composes.
|
|
86
|
+
- [Directory-picker capability seam decision](../../../.agents/notes/archived/architecture/2026-07-28-directory-picker-capability-seam.md) — why backends differ in interaction shape.
|
|
87
|
+
- [Native backend](../directory-picker-native/README.md) — the interaction mounted for a local operator.
|
|
88
|
+
- [Browse backend](../directory-picker-browse/README.md) — the interaction mounted everywhere else.
|
|
89
|
+
|
|
90
|
+
-----
|
|
91
|
+
|
|
92
|
+
<a id="model-experience"></a>
|
|
93
|
+
## Model Experience
|
|
94
|
+
|
|
95
|
+
None, as the GUI host's directory-selection chooser only mounts a backend row and registers nothing model-facing.
|
|
96
|
+
|
|
97
|
+
#### KV Cache effect
|
|
98
|
+
|
|
99
|
+
None; this package neither assembles nor sends a provider request.
|
|
100
|
+
|
|
101
|
+
## Known Limitations and Deferred Work
|
|
102
|
+
|
|
103
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
These limits define when the boot-time sample can misjudge the host. They are current package constraints, not a task backlog.
|
|
107
|
+
|
|
108
|
+
- **Detection infers operator location from launch context, which no launch-side signal can prove** — a tmux session detached from its SSH launch loses the `SSH_*` markers; a Darwin process outside an Aqua session still counts as displayed; and a workstation-local launch later reached through `ssh -L` arrives from `127.0.0.1`, resolves `native`, and opens the chooser on the unattended workstation. A wrong `native` choice degrades to the backend's existing retryable failure dialog, and composing `-browse` directly selects the safe interaction for such deployments.
|
|
109
|
+
- **The Linux chooser probe reads `PATH` only** — a zenity/kdialog reachable some other way (shell alias, non-PATH install) still resolves `browse`; installing either binary on `PATH` restores `native` eligibility at the next boot.
|
|
110
|
+
- **Boot-time only** — one resolution serves every client of the boot; per-connection adaptivity (native for a local browser, browse for a remote one, same server) would need a per-client capability and the wire advertisement the seam does not carry, and waits for a deployment that serves both at once.
|
|
111
|
+
|
|
112
|
+
<a id="dev-note"></a>
|
|
113
|
+
### Dev Note
|
|
114
|
+
|
|
115
|
+
<details>
|
|
116
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
117
|
+
|
|
118
|
+
None.
|
|
119
|
+
|
|
120
|
+
</details>
|
|
121
|
+
|
|
122
|
+
**Runtime invariant:** No companion is published. The sole effect is one boot-time Loader-entry mount owned by the plugin fiber; the store is authoritative.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "目录选择 seam 的自适应选择器:在启动时判定一次 web GUI 宿主的处境,并挂载匹配的原生或浏览后端。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @x1a0f3n9/dsh-host-directory-picker-auto
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
`dsh-host-directory-picker-auto` 为每次启动选出正确的目录选择交互:它在启动时一次性判定宿主处境,并把匹配的后端——[原生](../directory-picker-native/README.zh.md)或[浏览](../directory-picker-browse/README.zh.md)——连同其 browser 半侧一起,作为真实的 Loader 条目挂进内存根树。判定是一次纯函数的启动时采样:`native` 要求仅回环绑定、非 SSH 启动与可服务的显示会话;任何含糊情形都判定为处处可用的 `browse`。固定某种交互就是直接组合那个后端。挂载的能力在服务生命周期内保持稳定,符合 seam 的要求。
|
|
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
|
+
当同一份组合必须服务处境不同的宿主时,用本插件代替具体的后端:本地工作站会话里原生选择器可用,远程或无头会话里只有应用内浏览器可用。选择器在启动时检查一次宿主,并挂载匹配的交互。
|
|
29
|
+
|
|
30
|
+
### 选择是如何作出的
|
|
31
|
+
|
|
32
|
+
`native` 要求「操作者看得到宿主屏幕、且原生后端能服务它」的全部信号:仅回环的绑定(从注入的 `webServer` 读取;全网卡绑定会接入任何 OS 选择器都触及不到的远程浏览器);非 SSH 启动(共用的 [launch-environment](../../util/launch-environment/README.zh.md) 判断忽略项目与用户 `.env` 中的值,只检查继承的非空 `SSH_CONNECTION`/`SSH_TTY`);以及可服务的显示会话——darwin 与 win32 上视为存在;linux 上要求 `DISPLAY`/`WAYLAND_DISPLAY`,外加 `PATH` 上有 zenity 或 kdialog 二进制;其余任何平台上都不成立。任何含糊情形都判定为处处可用的 `browse`。
|
|
33
|
+
|
|
34
|
+
### 你会得到什么
|
|
35
|
+
|
|
36
|
+
判定出的交互以普通 Loader 条目的形式到达:后端注册 `ctx.directoryPicker`,其 browser 半侧被 client 模块表发现的方式与配置行完全相同,因此 seam 的「一行同时换两面」不变式依然成立。卸载该选择器会移除该条目,连同两面一起卸载。采样每次启动恰好发生一次,因此挂载的能力在服务生命周期内保持稳定。
|
|
37
|
+
|
|
38
|
+
### 固定某种交互
|
|
39
|
+
|
|
40
|
+
固定交互在这里不是配置字段:直接组合 `-native` 或 `-browse` 行来替代本行——那才是 seam 文档化的切换点。同时挂载选择器**和**某个后端行会明确报错(重复的 `directoryPicker` 服务、`single` 类 slot 中的重复 client 流程)。
|
|
41
|
+
|
|
42
|
+
### 可观察的失败
|
|
43
|
+
|
|
44
|
+
错误的 `native` 选择会退化为后端既有的可重试失败对话框,而不是坏掉的组合;对探查无法证明其处境的部署,直接组合 `-browse` 即选择安全的交互。
|
|
45
|
+
|
|
46
|
+
-----
|
|
47
|
+
|
|
48
|
+
<a id="understand-the-implementation"></a>
|
|
49
|
+
## 理解实现
|
|
50
|
+
|
|
51
|
+
<details>
|
|
52
|
+
<summary>实现细节——点击展开</summary>
|
|
53
|
+
|
|
54
|
+
### 设计理念
|
|
55
|
+
|
|
56
|
+
选择器是一次纯决策加一次挂载:`resolveDirectoryPickerBackend` 在启动时采样宿主事实并返回一个后端类型,`apply` 把匹配的后端与界面包作为真实 Loader 条目挂进内存根树——绝不持久化到配置文件,因为根树的 `write()` 是 no-op。该 effect 的 disposer 会移除两个条目并汇合其 fiber 的拆除,因此卸载只在所挂载交互的两面完全停稳后返回。
|
|
57
|
+
|
|
58
|
+
### 判定表
|
|
59
|
+
|
|
60
|
+
| 条件 | 后端 |
|
|
61
|
+
|---|---|
|
|
62
|
+
| 绑定宿主不是 `127.0.0.1` | `browse` |
|
|
63
|
+
| 存在 `SSH_CONNECTION` 或 `SSH_TTY` | `browse` |
|
|
64
|
+
| darwin 或 win32 | `native` |
|
|
65
|
+
| linux 且带选择器二进制与显示 | `native` |
|
|
66
|
+
| 其他任何情况 | `browse` |
|
|
67
|
+
|
|
68
|
+
### 源码地图
|
|
69
|
+
|
|
70
|
+
| 文件 | 职责 |
|
|
71
|
+
|---|---|
|
|
72
|
+
| [`src/index.ts`](src/index.ts) | 插件入口:`BACKEND_PACKAGES`/`SURFACE_PACKAGES` 映射、`apply` 挂载与卸载 |
|
|
73
|
+
| [`src/resolve.ts`](src/resolve.ts) | `resolveDirectoryPickerBackend`——纯函数的启动时决策 |
|
|
74
|
+
| [`src/probe.ts`](src/probe.ts) | 宿主探查:`hasLinuxChooserBinary`、`canExecute` |
|
|
75
|
+
|
|
76
|
+
</details>
|
|
77
|
+
|
|
78
|
+
-----
|
|
79
|
+
|
|
80
|
+
<a id="further-exploration"></a>
|
|
81
|
+
## 进一步探索
|
|
82
|
+
|
|
83
|
+
当选择器的约定不够用时阅读以下内容:先看 seam 定义,再看它挂载的两个后端。
|
|
84
|
+
|
|
85
|
+
- [目录选择 seam](../directory-picker/README.zh.md)——选择器所组合的能力约定。
|
|
86
|
+
- [目录选择能力 seam 决策](../../../.agents/notes/archived/architecture/2026-07-28-directory-picker-capability-seam.md)——后端为何在交互形态上彼此不同。
|
|
87
|
+
- [原生后端](../directory-picker-native/README.zh.md)——为本地操作者挂载的交互。
|
|
88
|
+
- [浏览后端](../directory-picker-browse/README.zh.md)——在其他任何地方挂载的交互。
|
|
89
|
+
|
|
90
|
+
-----
|
|
91
|
+
|
|
92
|
+
<a id="model-experience"></a>
|
|
93
|
+
## 模型体验
|
|
94
|
+
|
|
95
|
+
无。GUI 宿主的目录选择选择器只挂载一个后端行,不注册任何面向模型的内容。
|
|
96
|
+
|
|
97
|
+
#### KV Cache 影响
|
|
98
|
+
|
|
99
|
+
无;该包既不组装也不发送提供方请求。
|
|
100
|
+
|
|
101
|
+
## 已知限制与延期工作
|
|
102
|
+
|
|
103
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
这些限制说明启动时采样何时会误判宿主。它们是当前包约束,不是任务积压。
|
|
107
|
+
|
|
108
|
+
- **探测是从启动上下文推断操作者位置,而任何启动侧信号都无法证明这一点**——从 SSH 启动中脱离的 tmux 会话会丢失 `SSH_*` 标记;Aqua 会话之外的 Darwin 进程仍被算作有显示;在工作站本地启动、之后经 `ssh -L` 访问时,请求会从 `127.0.0.1` 到达,系统会判定 `native`,并把选择器弹在无人值守的工作站上。错误的 `native` 选择会退化为后端既有的可重试失败对话框,而对这类部署,直接组合 `-browse` 即选择安全的交互。
|
|
109
|
+
- **Linux 选择器探查只读 `PATH`**——以其他途径可用的 zenity/kdialog(shell 别名、未装在 PATH 上)仍判定为 `browse`;把任一二进制装到 `PATH` 上,下次启动即恢复 `native` 资格。
|
|
110
|
+
- **仅在启动时判定**——一次判定服务本次启动的所有客户端;按连接自适应(同一台服务器,本地浏览器用 native、远程浏览器用 browse)需要按客户端的能力对象以及 seam 未携带的协议通告,等到出现同时服务两种形态的部署再做。
|
|
111
|
+
|
|
112
|
+
<a id="dev-note"></a>
|
|
113
|
+
### 开发备注
|
|
114
|
+
|
|
115
|
+
<details>
|
|
116
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
117
|
+
|
|
118
|
+
无。
|
|
119
|
+
|
|
120
|
+
</details>
|
|
121
|
+
|
|
122
|
+
**运行时不变式:** 不发布伴生入口。唯一 effect 是由插件 fiber 持有的 boot-time Loader-entry mount,存储是权威来源。
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { launchEnvironmentOf, launchedThroughSsh } from "@x1a0f3n9/dsh-launch-environment";
|
|
2
|
+
import { accessSync, constants } from "node:fs";
|
|
3
|
+
import { delimiter, join } from "node:path";
|
|
4
|
+
//#region lib/types/probe.js
|
|
5
|
+
/**
|
|
6
|
+
* PATH probe for the native backend's Linux chooser binaries: one boot-time
|
|
7
|
+
* sampled fact for the resolver, so an attended Linux host without
|
|
8
|
+
* zenity/kdialog keeps the working `browse` interaction instead of a backend
|
|
9
|
+
* whose every pick fails.
|
|
10
|
+
* @module @x1a0f3n9/dsh-host-directory-picker-auto/probe
|
|
11
|
+
*/
|
|
12
|
+
/** The chooser binaries the native backend can drive on Linux (zenity, KDialog fallback). */
|
|
13
|
+
const LINUX_CHOOSER_BINARIES = ["zenity", "kdialog"];
|
|
14
|
+
/**
|
|
15
|
+
* Whether the current process may execute the candidate path.
|
|
16
|
+
* @param candidate - absolute or PATH-joined file path.
|
|
17
|
+
* @returns true only for an existing executable file.
|
|
18
|
+
*/
|
|
19
|
+
function canExecute(candidate) {
|
|
20
|
+
try {
|
|
21
|
+
accessSync(candidate, constants.X_OK);
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Scan a PATH value for one of the native backend's Linux chooser binaries.
|
|
29
|
+
* @param pathValue - the `PATH` environment value (absent or empty scans nothing).
|
|
30
|
+
* @param isExecutable - executability predicate ({@link canExecute} in production; injected for deterministic tests).
|
|
31
|
+
* @returns whether any PATH directory holds an executable chooser binary.
|
|
32
|
+
*/
|
|
33
|
+
function hasLinuxChooserBinary(pathValue, isExecutable) {
|
|
34
|
+
for (const dir of (pathValue ?? "").split(delimiter)) {
|
|
35
|
+
if (dir === "") continue;
|
|
36
|
+
for (const name of LINUX_CHOOSER_BINARIES) if (isExecutable(join(dir, name))) return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region lib/types/resolve.js
|
|
42
|
+
/**
|
|
43
|
+
* Boot-time backend resolution for the adaptive directory-picker composition:
|
|
44
|
+
* one pure decision from sampled host facts to a concrete backend kind. The
|
|
45
|
+
* caller samples exactly once per boot, so the mounted capability stays
|
|
46
|
+
* stable for the service lifetime as the seam requires.
|
|
47
|
+
* @module @x1a0f3n9/dsh-host-directory-picker-auto/resolve
|
|
48
|
+
*/
|
|
49
|
+
/** An env value counts only when set and non-blank (an empty export is "unset" by shell convention). */
|
|
50
|
+
const present = (value) => value !== void 0 && value !== "";
|
|
51
|
+
/**
|
|
52
|
+
* Resolve which backend serves this boot. `native` requires every signal that
|
|
53
|
+
* the operator can see the host display and the native backend can serve it:
|
|
54
|
+
* a loopback-only bind (an all-interfaces bind admits remote browsers no OS
|
|
55
|
+
* chooser can reach), no SSH launch (under SSH port-forwarding the chooser
|
|
56
|
+
* would open on the unattended server), and a servable display session —
|
|
57
|
+
* assumed on darwin/win32, requiring `DISPLAY`/`WAYLAND_DISPLAY` plus a
|
|
58
|
+
* chooser binary on linux, and never true elsewhere (the native backend
|
|
59
|
+
* drives exactly darwin/win32/linux). Anything ambiguous resolves to
|
|
60
|
+
* `browse`, which works everywhere.
|
|
61
|
+
* @param facts - the sampled host facts.
|
|
62
|
+
* @returns the backend kind to mount.
|
|
63
|
+
*/
|
|
64
|
+
function resolveDirectoryPickerBackend(facts) {
|
|
65
|
+
if (facts.bindHost !== "127.0.0.1") return "browse";
|
|
66
|
+
if (facts.ssh) return "browse";
|
|
67
|
+
if (facts.platform === "darwin" || facts.platform === "win32") return "native";
|
|
68
|
+
if (facts.platform !== "linux" || !facts.linuxChooser) return "browse";
|
|
69
|
+
return present(facts.env.DISPLAY) || present(facts.env.WAYLAND_DISPLAY) ? "native" : "browse";
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region lib/types/index.js
|
|
73
|
+
/**
|
|
74
|
+
* Adaptive chooser of the directory-picker seam: resolves the host's
|
|
75
|
+
* situation once at boot (bind host, SSH launch, display session, Linux
|
|
76
|
+
* chooser binary) and mounts the matching interaction — `native` or `browse`
|
|
77
|
+
* — as real Loader entries in the in-memory root tree. Each interaction is a
|
|
78
|
+
* pair: the Host backend serving the seam capability and the client surface
|
|
79
|
+
* occupying ui-workspace's directory-flow holes. Both arrive as ordinary
|
|
80
|
+
* entries, so the surface is discovered exactly as a config-row's would be
|
|
81
|
+
* and one resolved choice still swaps both faces; pinning an interaction
|
|
82
|
+
* remains composing that pair directly instead of this row.
|
|
83
|
+
* @module @x1a0f3n9/dsh-host-directory-picker-auto
|
|
84
|
+
*/
|
|
85
|
+
/** Cordis plugin name. */
|
|
86
|
+
const name = "directory-picker-auto";
|
|
87
|
+
/** Required services: the effective bind host (`webServer`) and the entry tree the backend mounts into (`loader`). */
|
|
88
|
+
const inject = ["webServer", "loader"];
|
|
89
|
+
/**
|
|
90
|
+
* Host backend package per resolved kind — fixed composition vocabulary, not a
|
|
91
|
+
* tunable. Exported because the reference is a runtime string the static
|
|
92
|
+
* config gate cannot see in a yml row: `verify-cordis-config` requires every
|
|
93
|
+
* app composing this chooser to declare both values as dependencies.
|
|
94
|
+
*/
|
|
95
|
+
const BACKEND_PACKAGES = {
|
|
96
|
+
native: "@x1a0f3n9/dsh-host-directory-picker-native",
|
|
97
|
+
browse: "@x1a0f3n9/dsh-host-directory-picker-browse"
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Client surface package per resolved kind, mounted with its backend so one
|
|
101
|
+
* resolved interaction still composes both faces. Declared as dependencies by
|
|
102
|
+
* every composing app for the same reason as {@link BACKEND_PACKAGES}. Only the
|
|
103
|
+
* specifier is referenced here because the packages belong to the Client
|
|
104
|
+
* program, so no import of them exists on this side.
|
|
105
|
+
*/
|
|
106
|
+
const SURFACE_PACKAGES = {
|
|
107
|
+
native: "@x1a0f3n9/dsh-client-ui-directory-picker-native",
|
|
108
|
+
browse: "@x1a0f3n9/dsh-client-ui-directory-picker-browse"
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Resolve the interaction from one boot-time sample and mount its backend and
|
|
112
|
+
* surface as Loader entries; the effect's disposer removes both entries and
|
|
113
|
+
* joins their fibers' teardown, so unloading this plugin returns only after
|
|
114
|
+
* both faces of the mounted interaction (and their dependents) quiesced.
|
|
115
|
+
* @param ctx - cordis context carrying the injected `webServer` and `loader`.
|
|
116
|
+
*/
|
|
117
|
+
async function apply(ctx) {
|
|
118
|
+
const backend = resolveDirectoryPickerBackend({
|
|
119
|
+
bindHost: ctx.webServer.host,
|
|
120
|
+
platform: process.platform,
|
|
121
|
+
ssh: launchedThroughSsh(launchEnvironmentOf(ctx)),
|
|
122
|
+
env: process.env,
|
|
123
|
+
linuxChooser: hasLinuxChooserBinary(process.env.PATH, canExecute)
|
|
124
|
+
});
|
|
125
|
+
await ctx.effect(async () => {
|
|
126
|
+
const ids = [];
|
|
127
|
+
const unmount = async () => {
|
|
128
|
+
for (const id of [...ids].reverse()) {
|
|
129
|
+
if (ctx.loader.store[id] === void 0) continue;
|
|
130
|
+
await ctx.loader.remove(id);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
try {
|
|
134
|
+
for (const name of [BACKEND_PACKAGES[backend], SURFACE_PACKAGES[backend]]) ids.push(await ctx.loader.create({ name }));
|
|
135
|
+
} catch (cause) {
|
|
136
|
+
await unmount();
|
|
137
|
+
throw cause;
|
|
138
|
+
}
|
|
139
|
+
return unmount;
|
|
140
|
+
}, "directory-picker-auto: interaction entries");
|
|
141
|
+
}
|
|
142
|
+
//#endregion
|
|
143
|
+
export { BACKEND_PACKAGES, SURFACE_PACKAGES, apply, canExecute, hasLinuxChooserBinary, inject, name, resolveDirectoryPickerBackend };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive chooser of the directory-picker seam: resolves the host's
|
|
3
|
+
* situation once at boot (bind host, SSH launch, display session, Linux
|
|
4
|
+
* chooser binary) and mounts the matching interaction — `native` or `browse`
|
|
5
|
+
* — as real Loader entries in the in-memory root tree. Each interaction is a
|
|
6
|
+
* pair: the Host backend serving the seam capability and the client surface
|
|
7
|
+
* occupying ui-workspace's directory-flow holes. Both arrive as ordinary
|
|
8
|
+
* entries, so the surface is discovered exactly as a config-row's would be
|
|
9
|
+
* and one resolved choice still swaps both faces; pinning an interaction
|
|
10
|
+
* remains composing that pair directly instead of this row.
|
|
11
|
+
* @module @x1a0f3n9/dsh-host-directory-picker-auto
|
|
12
|
+
*/
|
|
13
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
14
|
+
import type { DirectoryPickerBackendKind } from './resolve.ts';
|
|
15
|
+
export { canExecute, hasLinuxChooserBinary } from './probe.ts';
|
|
16
|
+
export type { DirectoryPickerBackendKind, DirectoryPickerEnv, DirectoryPickerHostFacts } from './resolve.ts';
|
|
17
|
+
export { resolveDirectoryPickerBackend } from './resolve.ts';
|
|
18
|
+
/** Cordis plugin name. */
|
|
19
|
+
export declare const name = "directory-picker-auto";
|
|
20
|
+
/** Required services: the effective bind host (`webServer`) and the entry tree the backend mounts into (`loader`). */
|
|
21
|
+
export declare const inject: string[];
|
|
22
|
+
/**
|
|
23
|
+
* Host backend package per resolved kind — fixed composition vocabulary, not a
|
|
24
|
+
* tunable. Exported because the reference is a runtime string the static
|
|
25
|
+
* config gate cannot see in a yml row: `verify-cordis-config` requires every
|
|
26
|
+
* app composing this chooser to declare both values as dependencies.
|
|
27
|
+
*/
|
|
28
|
+
export declare const BACKEND_PACKAGES: Record<DirectoryPickerBackendKind, string>;
|
|
29
|
+
/**
|
|
30
|
+
* Client surface package per resolved kind, mounted with its backend so one
|
|
31
|
+
* resolved interaction still composes both faces. Declared as dependencies by
|
|
32
|
+
* every composing app for the same reason as {@link BACKEND_PACKAGES}. Only the
|
|
33
|
+
* specifier is referenced here because the packages belong to the Client
|
|
34
|
+
* program, so no import of them exists on this side.
|
|
35
|
+
*/
|
|
36
|
+
export declare const SURFACE_PACKAGES: Record<DirectoryPickerBackendKind, string>;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the interaction from one boot-time sample and mount its backend and
|
|
39
|
+
* surface as Loader entries; the effect's disposer removes both entries and
|
|
40
|
+
* joins their fibers' teardown, so unloading this plugin returns only after
|
|
41
|
+
* both faces of the mounted interaction (and their dependents) quiesced.
|
|
42
|
+
* @param ctx - cordis context carrying the injected `webServer` and `loader`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function apply(ctx: Context): Promise<void>;
|
|
45
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PATH probe for the native backend's Linux chooser binaries: one boot-time
|
|
3
|
+
* sampled fact for the resolver, so an attended Linux host without
|
|
4
|
+
* zenity/kdialog keeps the working `browse` interaction instead of a backend
|
|
5
|
+
* whose every pick fails.
|
|
6
|
+
* @module @x1a0f3n9/dsh-host-directory-picker-auto/probe
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Whether the current process may execute the candidate path.
|
|
10
|
+
* @param candidate - absolute or PATH-joined file path.
|
|
11
|
+
* @returns true only for an existing executable file.
|
|
12
|
+
*/
|
|
13
|
+
export declare function canExecute(candidate: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Scan a PATH value for one of the native backend's Linux chooser binaries.
|
|
16
|
+
* @param pathValue - the `PATH` environment value (absent or empty scans nothing).
|
|
17
|
+
* @param isExecutable - executability predicate ({@link canExecute} in production; injected for deterministic tests).
|
|
18
|
+
* @returns whether any PATH directory holds an executable chooser binary.
|
|
19
|
+
*/
|
|
20
|
+
export declare function hasLinuxChooserBinary(pathValue: string | undefined, isExecutable: (candidate: string) => boolean): boolean;
|
|
21
|
+
//# sourceMappingURL=probe.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot-time backend resolution for the adaptive directory-picker composition:
|
|
3
|
+
* one pure decision from sampled host facts to a concrete backend kind. The
|
|
4
|
+
* caller samples exactly once per boot, so the mounted capability stays
|
|
5
|
+
* stable for the service lifetime as the seam requires.
|
|
6
|
+
* @module @x1a0f3n9/dsh-host-directory-picker-auto/resolve
|
|
7
|
+
*/
|
|
8
|
+
import type { Config as HttpServerConfig } from '@x1a0f3n9/dsh-host-webserver';
|
|
9
|
+
/** Concrete interaction backend the resolver chooses between. */
|
|
10
|
+
export type DirectoryPickerBackendKind = 'native' | 'browse';
|
|
11
|
+
/** Environment keys the resolution reads (a `process.env` subset). */
|
|
12
|
+
export type DirectoryPickerEnv = Readonly<Partial<Record<'DISPLAY' | 'WAYLAND_DISPLAY', string>>>;
|
|
13
|
+
/** Host facts the backend choice is a pure function of, sampled once at boot. */
|
|
14
|
+
export interface DirectoryPickerHostFacts {
|
|
15
|
+
/** Effective webserver bind host (the schema's closed loopback/all-interfaces union). */
|
|
16
|
+
bindHost: HttpServerConfig['host'];
|
|
17
|
+
/** Host process platform. */
|
|
18
|
+
platform: NodeJS.Platform;
|
|
19
|
+
/** SSH launch fact from the inherited process layer, independent of `.env` values. */
|
|
20
|
+
ssh: boolean;
|
|
21
|
+
/** Environment sample; DISPLAY/WAYLAND_DISPLAY marks a Linux display. */
|
|
22
|
+
env: DirectoryPickerEnv;
|
|
23
|
+
/** Whether a Linux chooser binary the native backend can drive (zenity/kdialog) is on PATH; consulted only when `platform` is linux. */
|
|
24
|
+
linuxChooser: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve which backend serves this boot. `native` requires every signal that
|
|
28
|
+
* the operator can see the host display and the native backend can serve it:
|
|
29
|
+
* a loopback-only bind (an all-interfaces bind admits remote browsers no OS
|
|
30
|
+
* chooser can reach), no SSH launch (under SSH port-forwarding the chooser
|
|
31
|
+
* would open on the unattended server), and a servable display session —
|
|
32
|
+
* assumed on darwin/win32, requiring `DISPLAY`/`WAYLAND_DISPLAY` plus a
|
|
33
|
+
* chooser binary on linux, and never true elsewhere (the native backend
|
|
34
|
+
* drives exactly darwin/win32/linux). Anything ambiguous resolves to
|
|
35
|
+
* `browse`, which works everywhere.
|
|
36
|
+
* @param facts - the sampled host facts.
|
|
37
|
+
* @returns the backend kind to mount.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveDirectoryPickerBackend(facts: DirectoryPickerHostFacts): DirectoryPickerBackendKind;
|
|
40
|
+
//# sourceMappingURL=resolve.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@x1a0f3n9/dsh-host-directory-picker-auto",
|
|
3
|
+
"description": "Adaptive chooser of the directory-picker seam: resolves the host situation at boot and mounts the native or browse backend for the DeepSeek Harness web GUI host",
|
|
4
|
+
"version": "0.1.5-rc.3",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/host/directory-picker-auto"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./src/*": "./src/*",
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"lib/index.js",
|
|
26
|
+
"lib/types/**/*.d.ts"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
31
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.3",
|
|
32
|
+
"@x1a0f3n9/dsh-client-ui-directory-picker-browse": "^0.1.5-rc.3",
|
|
33
|
+
"@x1a0f3n9/dsh-client-ui-directory-picker-native": "^0.1.5-rc.3",
|
|
34
|
+
"@x1a0f3n9/dsh-host-directory-picker-browse": "^0.1.5-rc.3",
|
|
35
|
+
"@x1a0f3n9/dsh-host-directory-picker-native": "^0.1.5-rc.3",
|
|
36
|
+
"@x1a0f3n9/dsh-host-webserver": "^0.1.5-rc.3"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
40
|
+
"@deepseek-ai/cordis-plugin-include": "^1.0.7",
|
|
41
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.3",
|
|
42
|
+
"@x1a0f3n9/dsh-client-ui-directory-picker-browse": "^0.1.5-rc.3",
|
|
43
|
+
"@x1a0f3n9/dsh-client-ui-directory-picker-native": "^0.1.5-rc.3",
|
|
44
|
+
"@x1a0f3n9/dsh-host-directory-picker": "^0.1.5-rc.3",
|
|
45
|
+
"@x1a0f3n9/dsh-host-directory-picker-browse": "^0.1.5-rc.3",
|
|
46
|
+
"@x1a0f3n9/dsh-host-directory-picker-native": "^0.1.5-rc.3",
|
|
47
|
+
"@x1a0f3n9/dsh-host-webserver": "^0.1.5-rc.3"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@x1a0f3n9/dsh-launch-environment": "^0.1.5-rc.3"
|
|
51
|
+
}
|
|
52
|
+
}
|