dsh-git-workspace 0.0.0-stage → 0.1.1

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 ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, dsh-github-workspace contributors
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md CHANGED
@@ -1,3 +1,218 @@
1
- # Temporary Holding Version
1
+ # dsh-git-workspace
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 在 DeepSeek Harness 的「新建工作区」按钮上,加一个「从 Git 仓库创建」的来源。
4
+
5
+ 点侧栏的 **Add workspace**(或新会话 Hero 里的工作区选择器),弹出的不再是原来的目录浏览对话框,
6
+ 而是两个页签:**本地目录** 和 **Git 仓库**。填仓库地址,插件把仓库拉到本地,
7
+ 再把路径交回给原生工作区流程,最后和手动选目录一样出现在工作区列表里。
8
+
9
+ ## 支持的仓库地址
10
+
11
+ | 形式 | 例子 | 说明 |
12
+ | --- | --- | --- |
13
+ | 短格式 | `deepseek-ai/DeepSeek-Harness` | 默认 github.com |
14
+ | HTTPS | `https://gitlab.com/group/repo`、`https://gitee.com/owner/repo.git` | 任意 Git 服务器,可带端口 |
15
+ | SSH 协议 | `ssh://git@gitlab.com/group/repo.git` | 走本机 SSH key |
16
+ | SCP 风格 | `git@github.com:owner/repo.git` | 走本机 SSH key |
17
+ | git 协议 | `git://example.com/team/repo.git` | 只读协议 |
18
+
19
+ 目标目录名取自地址的最后一段(去掉 `.git`),冲突时直接复用已有目录。
20
+
21
+ 明确拒绝 `file://`、本地绝对路径、`../` 与 `~` 开头的输入 —— 这不是 clone 本地目录的入口。
22
+
23
+ ## 它接管了什么
24
+
25
+ DSH 的工作区新增入口由 `@deepseek-ai/dsh-client-ui-workspace` 声明成两个「单占洞」:
26
+
27
+ | 入口 | 洞名 |
28
+ | --- | --- |
29
+ | 侧栏 Add workspace | `sidebar.workspaces.directoryFlow` |
30
+ | 新会话 Hero 的工作区选择器 | `conversation.hero.workspace.directoryFlow` |
31
+
32
+ 插件的浏览器半同时占用这两个洞。洞的契约是「每次打开回报一个本地绝对路径」,
33
+ 所以本插件不碰工作区注册表,克隆完调用 `onPicked(path)` 即可。
34
+
35
+ ### 和内置目录浏览器的关系
36
+
37
+ 内置的 `@deepseek-ai/dsh-client-ui-directory-picker-browse` 也占这两个洞,注册优先级是默认 0。
38
+ `dsh-client-ui-slots` 对 `single` 洞的规则是:同优先级再注册会直接抛错,不同优先级按升序投影取胜者。
39
+ 本插件用 **priority -1** 接管,所以原来的应用内目录浏览对话框不再显示。
40
+
41
+ 卸载本插件后遮蔽消失,内置对话框自动恢复 —— 不需要改 profile 配置。
42
+
43
+ ## 仓库怎么拉下来
44
+
45
+ host 半按两级尝试:
46
+
47
+ 1. **git clone**(`--depth 1`,可指定分支)。git 可执行文件按以下顺序探测:
48
+ - 环境变量 `DSH_GIT_EXE`
49
+ - `C:\Program Files\Git\cmd\git.exe`、`C:\Program Files\Git\bin\git.exe`、
50
+ `C:\Program Files (x86)\Git\cmd\git.exe`、`%LOCALAPPDATA%\Programs\Git\cmd\git.exe`
51
+ - 类 Unix:`/usr/bin/git`、`/usr/local/bin/git`、`/opt/homebrew/bin/git`
52
+ - 最后回退到 PATH 里的裸 `git`(`git --version` 探测)
53
+ 2. **codeload tarball 兜底 —— 仅 GitHub**:git 不存在或 clone 失败时,下载
54
+ `https://codeload.github.com/<owner>/<repo>/tar.gz/<ref>`,用系统 `tar` 解压并剥掉顶层目录。
55
+
56
+ > 非 GitHub 主机(GitLab、Gitee、自建服务器)**只有 git 一条路径**:它们没有等价的公开归档接口,
57
+ > 找不到 git 时会直接报错并提示安装 git,而不是悄悄降级。
58
+ > tarball 结果没有 `.git` 目录,后续 git 操作需要用户自己 `git init`。
59
+
60
+ 拉取目标默认是 `~/.dsh/github-workspaces/<repo>`;在对话框里填「克隆到」可以改父目录(该目录必须已存在)。
61
+
62
+ ## 后台任务、进度与角落浮层
63
+
64
+ 克隆不再是阻塞式请求。点「克隆并添加」后:
65
+
66
+ 1. host 立即返回一个 `jobId`,实际拉取在后台跑。
67
+ 2. 对话框收成**右下角浮层**,显示仓库名、进度条、百分比和阶段文字。
68
+ 3. 浮层用 `position: fixed`,不遮住主界面,用户可以继续在 DSH 里做别的事。
69
+ 4. 任务完成后浮层消失,路径交给 ui-workspace 注册成工作区。
70
+
71
+ 进度来自 `git clone --progress` 的 stderr 百分比解析;走 tarball 时按 `content-length` 折算下载进度(15%–90%),解压阶段固定 92%。
72
+
73
+ 页面重载后进行中的任务不会丢:打开对话框时先查 `GET /gh-workspace/jobs`,有活跃任务就恢复浮层。
74
+
75
+ > 洞契约要求 `open` 保持 true 直到回报路径,所以浮层期间 `open` 不变 —— 用户点浮层的 × 等同于取消(`onCancel`)。
76
+
77
+ ## 私有仓库凭据
78
+
79
+ 克隆私有仓库有三条路,按优先级:
80
+
81
+ 1. **复用的本机 git 凭据(默认)**:token 留空时,插件不覆盖 `credential.helper`,git 会照常调用你本机配置的凭据助手。本机是 `credential.helper = manager`(Git Credential Manager),凭据存在 Windows 凭据管理器里 —— 之前 `git clone` 能过,这里也能过。SSH 地址(`ssh://`、`git@host:path`)走本机 SSH key。
82
+ 2. **环境变量**:`GITHUB_TOKEN` 或 `GH_TOKEN`。
83
+ 3. **手动填 token**:「Git 仓库」页签底部的「访问令牌」,填该服务器的 Personal Access Token(GitHub `ghp_…`、GitLab `glpat-…` 等)。
84
+
85
+ 手动填的凭据只用于本次克隆,**不落盘、不记录**,页面关闭即丢。token 通过 git 的 `GIT_CONFIG_*` 环境变量注入,
86
+ 配置键按**仓库地址的实际主机**拼成 `http.https://<host>/.extraheader`,所以 GitHub / GitLab / 自建服务器都能用同一个输入框。
87
+ token **不进命令行参数**,不会出现在进程列表里,也不会写进 `.git/config`。GitHub 的 tarball 兜底同样支持,用 `Authorization: Bearer`。
88
+
89
+ 权限范围:GitHub 需要 `repo` scope;GitLab 需要 `read_repository` 或 `api`。
90
+
91
+ 认证类失败会单独提示:`git` 输出里出现 `Authentication failed` / `could not read Username` / `Repository not found` / `403` 时,
92
+ 浮层会直接告诉你"本机凭据没覆盖该仓库,请填令牌",而不是丢一段原始 stderr。
93
+
94
+ ## 路由
95
+
96
+ host 半注册一组仅限回环的 HTTP 路由:
97
+
98
+ ```
99
+ POST /gh-workspace/clone { repo, branch?, parentDir?, token? } -> { job }
100
+ GET /gh-workspace/job?id=xxx -> { job }
101
+ POST /gh-workspace/job/cancel { id } -> { cancelled }
102
+ GET /gh-workspace/jobs -> { jobs } 仅进行中的任务
103
+
104
+ job = { id, repo, state, percent, detail, path?, method?, error? }
105
+ state ∈ running | done | failed | cancelled
106
+ ```
107
+
108
+ 非回环请求一律 403。目标目录已存在且非空时任务立即标记 `done`,`method` 为 `existing`。
109
+ 完成的任务保留 10 分钟供客户端取结果,之后自动清理。
110
+
111
+ ## 配置
112
+
113
+ | 环境变量 | 默认 | 说明 |
114
+ | --- | --- | --- |
115
+ | `DSH_GIT_WORKSPACE_ROOT` | `~/.dsh/github-workspaces` | 默认克隆父目录 |
116
+ | `DSH_GITHUB_WORKSPACE_ROOT` | 空 | 旧变量名,仅在未设置新名时生效 |
117
+ | `DSH_GIT_EXE` | 空 | 显式指定 git 可执行文件路径 |
118
+ | `GITHUB_TOKEN` / `GH_TOKEN` | 空 | 私有仓库凭据,低于手动填写 |
119
+
120
+ ## 平台支持
121
+
122
+ | 平台 | 状态 | 说明 |
123
+ | --- | --- | --- |
124
+ | Desktop(Windows/macOS/Linux) | 支持 | 系统文件夹选择器可用时显示按钮 |
125
+ | Web(`dsh web`,浏览器访问) | 支持 | 用内置目录浏览;`pickDirectory` 不可用时不显示按钮 |
126
+
127
+ 代码层面本来就是 Web 形态:
128
+
129
+ - `dsh.client.platform` 是 `web`,client bundle 走 Module Loader,desktop 与 web 共用同一份。
130
+ - host 半只依赖 node 内置模块(`child_process` / `fs` / `path` / `os` / `crypto`),没有任何 DSH 内部 API 依赖。
131
+ - git / tar 可执行文件按「环境变量 → 常见安装路径 → PATH 里的裸命令」三级探测,Linux 容器和 nix 环境同样能找到。
132
+
133
+ ### 仅本机页面可调用
134
+
135
+ 所有路由拒绝非回环来源,**没有开关**。除此之外还有三道针对浏览器攻击的防线:
136
+
137
+ | 防线 | 挡住什么 |
138
+ | --- | --- |
139
+ | `Host` 必须是 `127.0.0.1` / `localhost` / `[::1]` | DNS rebinding(任意域名解析到回环) |
140
+ | POST 必须 `content-type: application/json` | 跨站简单请求:非简单类型会触发 preflight,而本服务不响应 preflight |
141
+ | `Origin` 存在时必须是回环源 | 同源策略覆盖不到的场景 |
142
+
143
+ 只看 `socket.remoteAddress` 是不够的 —— 跨站请求同样从 127.0.0.1 出发。
144
+
145
+ 另外两条写入约束:
146
+
147
+ - 用户指定的 `parentDir` **必须已存在且是目录**,不会替调用方创建任意路径。
148
+ - 同时最多 3 个克隆任务,超出的请求返回 429。
149
+
150
+ 需要从别的设备使用时,走 SSH 隧道把端口转回本地,remoteAddress 仍然是回环:
151
+
152
+ ```bash
153
+ ssh -L 52280:127.0.0.1:52280 user@host
154
+ ```
155
+
156
+ ### Web 端的系统文件夹选择器
157
+
158
+ Web 端通常没有 `pickDirectory`,本地页签只显示路径输入和内置目录浏览。这是预期行为,不是故障。
159
+
160
+ ## 安装
161
+
162
+ ```bash
163
+ # 从 npm
164
+ dsh plugin --profile web add dsh-git-workspace
165
+
166
+ # 从 GitHub 源码
167
+ dsh plugin --profile web add github:OWNER/dsh-git-workspace
168
+
169
+ # 从本地目录(开发)
170
+ dsh plugin --profile web add /absolute/path/to/dsh-git-workspace
171
+ ```
172
+
173
+ 装完重启 GUI / `dsh web`。host 行由 `cordis.patch.yml` 声明,client 由 `dsh.client` 声明驱动。
174
+
175
+ ## 发布
176
+
177
+ ```bash
178
+ npm run build:all # host → lib/index.js,client → lib/client.js
179
+ npm pack # 产出 tgz
180
+ npm publish # 或 gh release create v0.1.0 <tgz>
181
+ ```
182
+
183
+ `files` 只包含 `lib`、`cordis.patch.yml`、`README.md`、`LICENSE`。<br>
184
+ 包名是无 scope 的 `dsh-git-workspace`,`package.json` 的 `name` 与 `cordis.patch.yml` 的 `id` / `name` 必须一致。
185
+ client bundle 的 `__ModuleLoader__.load({ id })` 注册 id 由 `tsdown.config.ts` 从 `package.json.name` 派生,**禁止手写别名**:
186
+ DSH 的 client-modules 用包名当 boot graph 的 row id,注册 id 不一致会让同一个 bundle 被执行两遍,
187
+ 撞上 `duplicate factory registration`,整个 web boot 失败、渲染层恢复耗尽后 DSH NEXT 直接退出
188
+ (2026-10-28 的启动崩溃就是写死的旧别名 `@dsh-external/dsh-git-workspace` 引起的)。
189
+
190
+ ## 构建
191
+
192
+ host 半不依赖任何 DSH 包或第三方运行时库,构建只需要本地安装的 TypeScript:
193
+
194
+ ```bash
195
+ npm install
196
+ npm run build:all # host → lib/index.js,client → lib/client.js
197
+ ```
198
+
199
+ `scripts/build.mjs` 直接调用 node,不经过 shell —— 这样在 WSL / Git Bash /
200
+ 纯 Windows 下都能跑(本机 `bash` 是 WSL 的,非登录 shell 里 PATH 不含 Windows 的 node)。
201
+
202
+ 装配(桌面 profile):
203
+
204
+ ```text
205
+ dev_install_package { dir: "C:/Users/admin/Documents/dsh-infra/plugins/dsh-git-workspace", profile: "desktop" }
206
+ ```
207
+
208
+ 装配器默认往 `profiles/web` 注入,而桌面 GUI 跑的是 `profiles/desktop` —— 必须显式指定 `profile: "desktop"`,
209
+ 否则重启后 client 半不会被加载。
210
+
211
+ ## 已知限制
212
+
213
+ - 接管是全局的:只要本插件在,内置目录浏览对话框就不出现,本地目录只能靠路径输入或系统选择器。
214
+ - 本地目录页签的系统选择器依赖 `__DSH_DIRECTORY_PICKER__` 或 `uiWorkspace.pickDirectory()`,两者都没有时只显示路径输入和目录列表;
215
+ 非 Windows 桌面环境只显示路径输入框。
216
+ - **codeload tarball 兜底只覆盖 GitHub**。GitLab、Gitee、自建服务器在没有 git 时会直接报错,不会降级。
217
+ - tarball 路径上限 256 MB,单次 git 操作超时 15 分钟。
218
+ - `existing` 判定只看目标目录是否非空,不校验它是否来自同一个仓库 —— 同名不同仓库时会复用已有目录。
@@ -0,0 +1,9 @@
1
+ # dsh-git-workspace bundle 层:插入 host 侧插件行。
2
+ # host 提供 /gh-workspace 下的 clone 任务路由;client 由 dsh.client 声明驱动,
3
+ # 接管 ui-workspace 的两个 directory-flow 洞。
4
+ #
5
+ # id 与包名保持一致:安装器按包名去重/卸载时,重复安装不会产生两个 entry id。
6
+ - insert:
7
+ - id: dsh-git-workspace
8
+ name: dsh-git-workspace
9
+ config: {}