@xia-sc/dsh-git 0.5.1 → 0.5.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.en.md +308 -284
- package/README.md +241 -225
- package/cordis.patch.yml +15 -15
- package/lib/client.js +2479 -2286
- package/lib/index.js +1292 -1272
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,225 +1,241 @@
|
|
|
1
|
-
# @xia-sc/dsh-git
|
|
2
|
-
|
|
3
|
-
[English](./README.en.md) | 中文
|
|
4
|
-
|
|
5
|
-
DeepSeek Harness Web GUI 的完整 Git 管理插件,形态为一个**可折叠的悬浮面板**,
|
|
6
|
-
**实时跟随当前会话的工作区**——在侧边栏点击不同的会话/工作区,面板会瞬间
|
|
7
|
-
重新绑定到对应仓库。
|
|
8
|
-
|
|
9
|
-
**支持的工作流:** 分支切换 · 拉取更新(fetch) · 拉取合并(pull,仅快进) ·
|
|
10
|
-
暂存全部 · 提交(commit,可用 AI 起草提交信息) · 推送(push) · 状态(status) ·
|
|
11
|
-
最近提交 · 未提交文件列表 · **点击变更看差异** · 基于某分支新建分支。
|
|
12
|
-
|
|
13
|
-
## 界面
|
|
14
|
-
|
|
15
|
-
- **悬浮面板**(`shell.overlay`):折叠时不渲染任何元素(不会遮挡输入框);
|
|
16
|
-
展开后是完整的 Git 工作台(状态行、带 **脏树预检** 的分支切换器——在有
|
|
17
|
-
未提交修改时选择分支,会先显示受影响文件列表警告而不是直接切换,并提供
|
|
18
|
-
"仍要切换"按钮;fetch/pull 操作;**提交区**——"暂存全部"按钮、AI 生成依据
|
|
19
|
-
选择器、"✨ AI 生成"按钮、提交信息输入框(多行文本框,回车换行、
|
|
20
|
-
Ctrl/Cmd+Enter 提交)与提交按钮;可折叠的变更列表与
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
`
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
`
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
`
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
`
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
|
161
|
-
|
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
>
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
-
|
|
224
|
-
|
|
225
|
-
|
|
1
|
+
# @xia-sc/dsh-git
|
|
2
|
+
|
|
3
|
+
[English](./README.en.md) | 中文
|
|
4
|
+
|
|
5
|
+
DeepSeek Harness Web GUI 的完整 Git 管理插件,形态为一个**可折叠的悬浮面板**,
|
|
6
|
+
**实时跟随当前会话的工作区**——在侧边栏点击不同的会话/工作区,面板会瞬间
|
|
7
|
+
重新绑定到对应仓库。
|
|
8
|
+
|
|
9
|
+
**支持的工作流:** 分支切换 · 拉取更新(fetch) · 拉取合并(pull,仅快进) ·
|
|
10
|
+
暂存全部 · 提交(commit,可用 AI 起草提交信息) · 推送(push) · 状态(status) ·
|
|
11
|
+
最近提交 · 未提交文件列表 · **点击变更看差异** · 基于某分支新建分支。
|
|
12
|
+
|
|
13
|
+
## 界面
|
|
14
|
+
|
|
15
|
+
- **悬浮面板**(`shell.overlay`):折叠时不渲染任何元素(不会遮挡输入框);
|
|
16
|
+
展开后是完整的 Git 工作台(状态行、带 **脏树预检** 的分支切换器——在有
|
|
17
|
+
未提交修改时选择分支,会先显示受影响文件列表警告而不是直接切换,并提供
|
|
18
|
+
"仍要切换"按钮;fetch/pull 操作;**提交区**——"暂存全部"按钮、AI 生成依据
|
|
19
|
+
选择器、"✨ AI 生成"按钮、提交信息输入框(多行文本框,回车换行、
|
|
20
|
+
Ctrl/Cmd+Enter 提交)与提交按钮;可折叠的变更列表与
|
|
21
|
+
最近提交列表)。**操作反馈固定钉在面板最上边,而且只有一行**——"推送中…"和上次操作的结果都在
|
|
22
|
+
body 的第一行;结果是一个中文短句("已推送"/"已暂存全部"/"已切换到 xxx"),git 自己的输出
|
|
23
|
+
(push 的 sideband banner、`LF will be replaced by CRLF` 这类提示)收在右侧的"详情 ▾"里,点开才展开,
|
|
24
|
+
不会被变更列表和最近提交顶到看不见的地方。面板**可通过顶栏拖动**(按住带 Git 标题的
|
|
25
|
+
那一行,拖到哪里就停在哪里,不会拖出视口;顶栏上的按钮/输入框不会触发
|
|
26
|
+
拖动;双击顶栏回到居中位置)。
|
|
27
|
+
分支切换器旁边的"+ 新建分支"按钮会展开一个内联表单:新分支名 + 基分支
|
|
28
|
+
选择器(本地分支或 `origin/feature/x` 这样的完整远端引用)——确认后从
|
|
29
|
+
该基分支创建新分支并切换过去。
|
|
30
|
+
- **点击变更看差异**:点变更列表里的任意一行,面板会从 400px 的单栏展开成
|
|
31
|
+
两栏——左边照旧是完整工作台,右边是该文件的 unified diff(见下文
|
|
32
|
+
「差异查看」)。再点同一行、或点差异标题栏的 `×`,就收回单栏。
|
|
33
|
+
- **输入框胶囊**(`conversation.input.dock`):输入框左上角的紧凑型左对齐
|
|
34
|
+
状态胶囊(分支摘要,或"当前工作区不是 Git 仓库");点击它展开/收起悬浮面板。
|
|
35
|
+
- 两处界面共享同一个 store,状态永远一致,并都会随当前会话(及其 cwd)
|
|
36
|
+
切换而重新绑定。切换工作区会清空已选中的文件——绝不让面板显示别的仓库的内容。
|
|
37
|
+
- **git 的文字输出会剥掉 ANSI 颜色码**:远端会给自己的 banner 上色(gitee 的
|
|
38
|
+
`Powered by GITEE.COM` 就是),面板是 DOM 不是终端,ESC 没有字形可渲染,
|
|
39
|
+
不剥就只剩 `[0[01;33m` 这类参数当正文显示。只剥"消息"文本(`message`),
|
|
40
|
+
**diff 内容与文件名一律逐字节保真**。
|
|
41
|
+
|
|
42
|
+
### 差异查看
|
|
43
|
+
|
|
44
|
+
点变更列表里的一行,就在右侧显示该文件的 git diff。左侧列表是它的导航:
|
|
45
|
+
行高亮表示"正在看这一个",首次点击会自动把变更列表展开成完整列表。
|
|
46
|
+
|
|
47
|
+
- **可拖拽调宽**:拖动**面板最右侧那条边**即改差异栏宽度——左栏(工作台)宽度不变,
|
|
48
|
+
面板整体变宽、差异栏跟着长;双击这条边复位。面板始终留在视口内,窗口变窄时
|
|
49
|
+
会自动收回;拖动时面板会就地锚定左边缘,所以指针与这条边是 1:1 跟手的。
|
|
50
|
+
- **未暂存 / 已暂存**:默认自动跟随数据——未暂存有内容就显示未暂存,否则显示
|
|
51
|
+
已暂存;顶部两个 chip 可手动切换,空的一侧置灰。同一个文件两边都有改动时,
|
|
52
|
+
一次点击看到的是最关心的那一侧,另一侧一键可达。
|
|
53
|
+
- **自动刷新**:暂存、提交、切分支、刷新之后,打开的差异会自己重读——未暂存
|
|
54
|
+
变空时会自动落到已暂存。
|
|
55
|
+
- **行号与配色**:左右两列行号(旧/新)取自每个 hunk 头,`+`/`-` 行走主题的
|
|
56
|
+
成功/错误色,hunk 头单独一行,`` 灰显。
|
|
57
|
+
- **边界都有明确提示**:这一侧没有改动 / 二进制文件没有文本差异 / 差异过大只
|
|
58
|
+
显示开头一段 / 未跟踪目录里还有 N 个文件未展开 / 读取失败。超过 1500 行的
|
|
59
|
+
差异先渲染前 1500 行,按钮可展开全部(避免一次渲染几万个节点)。
|
|
60
|
+
- **未跟踪文件**:与空文件对比,显示成 `new file mode` 的新增 diff;未跟踪
|
|
61
|
+
**目录**会把里面的文件逐个展开(上限 50 个,其余计入提示)。
|
|
62
|
+
- **重命名**:同时把旧名与新名作为 pathspec 交给 git,否则 git 无法配对,会把
|
|
63
|
+
一次重命名报成整文件新增。
|
|
64
|
+
|
|
65
|
+
### 提交区与 AI 起草
|
|
66
|
+
|
|
67
|
+
提交区按真实操作顺序排列:**暂存全部 → AI 生成 → 提交**。
|
|
68
|
+
|
|
69
|
+
- **暂存全部**:`git add --all`(含删除与未跟踪文件)。工作区干净时置灰。
|
|
70
|
+
这是"只有未暂存改动、点提交却报错"的正解——提交本身依然**不会**隐式暂存。
|
|
71
|
+
- **AI 生成依据**:三选一,决定把哪一部分改动交给模型:
|
|
72
|
+
`已暂存`(默认)、`未暂存`、`全部`。默认 `已暂存`,因为**只有它是本次
|
|
73
|
+
提交真正会记录的内容**——用其他依据生成的描述可能与实际提交不符。
|
|
74
|
+
- **✨ AI 生成**:把选中的改动(diffstat + diff,截断后)交给当前会话所选
|
|
75
|
+
模型,生成的提交信息直接填入输入框;不满意可改,也可以直接手写。
|
|
76
|
+
|
|
77
|
+
生成用的模型路由取当前会话的 `modelSelection` 投影(待生效的选择优先,
|
|
78
|
+
其次是上次实际使用),取不到时回落到宿主注册的第一条路由。失败会以
|
|
79
|
+
本地化文案显示在"上次操作输出"里(无可用改动 / 未配置模型 / 生成失败等)。
|
|
80
|
+
|
|
81
|
+
## 架构
|
|
82
|
+
|
|
83
|
+
一个双面 npm 包:
|
|
84
|
+
|
|
85
|
+
| 半边 | 文件 | 职责 |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| 宿主 | `lib/index.js` | Cordis 插件(bundle 行 `dsh-git`),在自己的 `ctx.webServer` 上注册 `/dsh-git-rpc` 前缀路由,收发浏览器 `connection.rpc.call` 的同一套 Connection RPC 信封,并复用 connection 服务的 Host/Origin + 浏览器会话围栏(`connection.requestRejection`)。端点:`status`、`branches`、`checkout`、`createBranch`、`fetch`、`pull`、`stage`、`diff`、`commit`、`push`、`log`、`generateMessage`。所有 git 调用都走 `execFile`(无 shell)、带超时(本地 30s / 网络 120s)、严格入参校验。AI 生成走注入的 `llm` 服务。 |
|
|
88
|
+
| 浏览器 | `lib/client.js` | `dsh.client` bundle(服务于 `/plugins/@xia-sc/dsh-git/client.js`):悬浮面板 + dock 行 + 共享 store,对照模块表手写(仅依赖 `react`)。 |
|
|
89
|
+
|
|
90
|
+
### 为什么自持 HTTP 路由(dsh ≥ 0.1.5-rc.1)
|
|
91
|
+
|
|
92
|
+
dsh 0.1.5-rc.1 起,外部插件不能再调用 `ctx.connection.rpc.handle()`:
|
|
93
|
+
`HostConnectionService.rpc` 闭包持有的是 **connection 插件自己的 Context**(`inject`
|
|
94
|
+
只有 `["credentials"]`),注册时执行
|
|
95
|
+
`owner.effect(() => owner.webServer.register(route))`,而该插件只在内部的
|
|
96
|
+
`ctx.inject(["webServer"], …)` 作用域里取得到 `webServer`。于是无论调用方 inject 了
|
|
97
|
+
什么,这一行都会以 `cannot get property "webServer" without inject` 挂载失败
|
|
98
|
+
(0.3.0 正是如此,插件在 0.1.5-rc.1 上装不起来)。本插件因此改为自己注册
|
|
99
|
+
`/dsh-git-rpc` 路由、自己实现同一套 RPC 信封;请求围栏仍交给 connection 服务的
|
|
100
|
+
`requestRejection`,安全等级与 `/api` 完全一致。`test/host-mount.mjs` 在真实 Cordis +
|
|
101
|
+
真实 Connection 服务上守护这一点。
|
|
102
|
+
|
|
103
|
+
### 会话绑定由会话座位提供(dsh ≥ 0.1.6-alpha.2)
|
|
104
|
+
|
|
105
|
+
两个界面此前都从 sessions 列表快照里读 `current`(当前会话 id)。0.1.6-alpha.2 把这个
|
|
106
|
+
字段去掉了——列表快照只剩 `ids` / `byId` / `phase` / `subagentsByParent` /
|
|
107
|
+
`jobsBySession`,当前会话改由渲染器的作用域适配器(`SlotScopeAdapter.current`,由
|
|
108
|
+
`retainedBy.mainView` 推出)投递给**会话作用域**的座位,根作用域的 `shell.overlay`
|
|
109
|
+
读不到它(症状是胶囊和面板一起静默消失,没有任何报错)。
|
|
110
|
+
|
|
111
|
+
现在:**胶囊**(`conversation.input.dock`,会话作用域,框架直接给出 `sessionId`)
|
|
112
|
+
从 `useSessions` 快照里取该会话的 `cwd`,调用 `store.bindSession(sessionId, cwd)`;
|
|
113
|
+
**面板**只读共享 store 的 `sessionId` / `cwd`(它另外用 `sessionId` 取
|
|
114
|
+
`modelSelection` 投影来定 AI 起草的路由)。`byId[id].retainedBy.mainView` 用来排除
|
|
115
|
+
右栏里的嵌入式会话(子会话 chat),快照不带这个计数时按主视图放行。
|
|
116
|
+
|
|
117
|
+
## 安装
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
dsh plugin --profile web add @xia-sc/dsh-git
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
也可以直接从源码装(两个渠道同源,npm 上的版本就是对应 tag 的产物):
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
dsh plugin --profile web add https://github.com/xia-sc/dsh-git
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
然后**重启 `dsh web`**(bundle 行与浏览器 roster 在启动时组合)。刷新后,
|
|
130
|
+
当前会话工作区是 git 仓库时,输入框上方会出现 dock 胶囊,点击即可展开面板。
|
|
131
|
+
|
|
132
|
+
要求 **dsh ≥ 0.1.5-rc.1**(宿主半自持 `/dsh-git-rpc` 路由,见上文架构说明)。
|
|
133
|
+
|
|
134
|
+
卸载:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
dsh plugin --profile web remove @xia-sc/dsh-git
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## RPC 约定(`/dsh-git-rpc`)
|
|
141
|
+
|
|
142
|
+
浏览器侧通过 `ctx.connection.rpc.call("/dsh-git-rpc", endpoint, { args })` 调用;宿主侧
|
|
143
|
+
是本插件自持的 `/dsh-git-rpc/*` 前缀路由,收发与 `/api` 相同的 Connection 信封:
|
|
144
|
+
|
|
145
|
+
- 请求:`POST /dsh-git-rpc/<endpoint>`,`content-type: application/json`,
|
|
146
|
+
`{ type: "client-request", rpcId, method: <endpoint>, payload: { args } }`
|
|
147
|
+
- 响应:`{ type: "server-response", rpcId, result: { ok: true, value } | { ok: false, error } }`
|
|
148
|
+
- 围栏:`connection.requestRejection`(Host/Origin + 浏览器会话 Cookie);非 `POST` → 405,
|
|
149
|
+
非 JSON → 415,请求体超限 → 413,路径不属于本通道 → 404。
|
|
150
|
+
|
|
151
|
+
载荷使用 `{ args }` 约定。`cwd` 必须是绝对路径;`branch` 匹配
|
|
152
|
+
`^[A-Za-z0-9][A-Za-z0-9._/-]*$`(不允许前导 `-`、`..`、`@{`、`\`、空白、
|
|
153
|
+
控制字符);`remote` 为普通单段。提交信息是真正的提交信息:一行主题 +
|
|
154
|
+
可选的多行正文。CRLF 会归一为 LF,逐行去掉行尾空白,去掉首尾空行并把连续
|
|
155
|
+
空行折叠为一行(因此主题与正文之间那一行空行会保留);空、超长(>10000
|
|
156
|
+
字符)、含控制字符的信息报 `invalid-message`。信息经 stdin 以
|
|
157
|
+
`git commit --cleanup=whitespace --file=-` 传入,因此空格、引号、换行、shell
|
|
158
|
+
元字符、前导 `-` 都会被原样记录。
|
|
159
|
+
|
|
160
|
+
| 端点 | 参数 | 结果(`value`) |
|
|
161
|
+
| --- | --- | --- |
|
|
162
|
+
| `status` | `{ cwd }` | `{ repo, branch, detached, oid, upstream, ahead, behind, dirty, changes: [{status, path, index, worktree, file, origFile}] }`。`path` 是展示串(重命名读作 `old → new`),`file`/`origFile` 是交给 `diff` 的 pathspec,`index`/`worktree` 是 porcelain-v2 的两个字母。 |
|
|
163
|
+
| `branches` | `{ cwd }` | `{ repo, current, local: [{name, current, upstream, sha}], remote: [{name, short}] }` |
|
|
164
|
+
| `checkout` | `{ cwd, branch }` | `{ branch, detached, oid, message? }`,经 `git switch --guess`;浏览器会预检脏树并提前警告;因"本地修改会被覆盖"被拒绝时会带上可读前缀。 |
|
|
165
|
+
| `createBranch` | `{ cwd, branch, base? }` | `{ branch, detached, oid, message? }`,经 `git switch --create <branch> <base>`(缺省 base 即 HEAD);从基分支创建新分支并切换过去。 |
|
|
166
|
+
| `fetch` | `{ cwd, remote? }` | `{ message }`(120s 超时) |
|
|
167
|
+
| `pull` | `{ cwd }` | `{ message }`,经 `git pull --ff-only`(绝不隐式合并) |
|
|
168
|
+
| `stage` | `{ cwd }` | `{ message }`,经 `git add --all` |
|
|
169
|
+
| `diff` | `{ cwd, path, origPath? }` | `{ repo, path, origPath, untracked, skipped, worktree: {diff, binary, truncated}, index: {…} }`。两侧一次读回(`git diff [--cached] --no-ext-diff --no-color -- <path> [<origPath>]`);`path` 必须是仓库内相对路径(拒绝绝对路径、`..`、前导 `-`、控制字符、首尾空白),非法时报 `invalid-path`。未跟踪路径用 `git diff --no-index -- /dev/null <path>`(容忍退出码 1),未跟踪目录用 `git ls-files --others --exclude-standard` 展开(上限 50 个,其余计入 `skipped`)。单侧超过 40 万字符在行边界截断并置 `truncated`;二进制置 `binary`。**只读**,不碰 index / 工作区 / 配置。 |
|
|
170
|
+
| `commit` | `{ cwd, message }` | `{ message }`;未配置 `user.name/email` 时报 `missing-author` 错误 |
|
|
171
|
+
| `push` | `{ cwd }` | `{ message }`(120s 超时) |
|
|
172
|
+
| `log` | `{ cwd, count? }` | `{ repo, commits: [{sha, author, subject, refs}] }`(钳制 1..50) |
|
|
173
|
+
| `generateMessage` | `{ cwd, mode?, provider?, model? }` | `{ message, mode, provider, model }`。`mode` 为 `staged`(默认)/`unstaged`/`all`,非法值报 `invalid-mode`;失败码见 `error.details.code`:`no-changes`、`no-provider`、`no-model`、`llm-truncated`(输出上限用尽、一个字都没写出来)、`llm-empty`、`cancelled`、`llm-failed`。 |
|
|
174
|
+
|
|
175
|
+
> 失败结果的 `error.code` 在线路上固定为 `"internal"`(Connection 信封只要求它是字符串),
|
|
176
|
+
> 插件自己的诊断码放在 `error.details.code`;客户端按该码做本地化文案。
|
|
177
|
+
>
|
|
178
|
+
> `fetch`/`pull`/`push`/`stage`/`commit` 的 `message` 就是 **git 自己的输出**(已剥掉 ANSI 颜色码):
|
|
179
|
+
> git 什么都没说时是空串,界面用 `output.<action>` 的中文短句做通知、把这段原文放进可展开的"详情"。
|
|
180
|
+
|
|
181
|
+
## 设计决策与边界
|
|
182
|
+
|
|
183
|
+
- **pull 固定 `--ff-only`**:不产生意外的合并提交;冲突以错误形式呈现,
|
|
184
|
+
由用户在自己的工具里解决。
|
|
185
|
+
- **commit 不暂存**:只提交已暂存的内容。想一次提交全部改动,用提交区的
|
|
186
|
+
"暂存全部"按钮(`git add --all`),而不是让提交隐式暂存。
|
|
187
|
+
- **AI 生成会把改动的 diff 发给你配置的模型提供方**——可能是第三方网关。
|
|
188
|
+
这是显式点击"✨ AI 生成"才会发生的联网行为;插件本身不联网。diff 截断到
|
|
189
|
+
12000 字符后发送,且不发送任何仓库外的内容。输出上限 8192 token:思考型
|
|
190
|
+
模型的 reasoning 与正文共用同一份 completion 预算,上限太小会"一个字都没
|
|
191
|
+
写就超限",此时报 `llm-truncated` 而不是含糊的 `llm-empty`。
|
|
192
|
+
- **push/pull 凭据**来自系统(Git Credential Manager / SSH agent);插件
|
|
193
|
+
绝不碰凭据存储。AI 生成同样不接触凭据——API key 由模型适配器自己解析。
|
|
194
|
+
- **插件绝不修改 git config**;缺 author 时给出明确错误而不是悄悄补写。
|
|
195
|
+
- **差异查看是纯只读的**:`diff` 端点只跑 `git diff` / `git ls-files`,不写
|
|
196
|
+
index、不动工作区、不改配置;它**不依赖**宿主右侧 Sidebar 那套标签页 API
|
|
197
|
+
(那部分还在快速迭代),而是面板内自带两栏——左侧工作台照旧,右侧差异栏。
|
|
198
|
+
差异渲染也是自己写的(本 bundle 只依赖 `react`):解析统一 diff、双行号、
|
|
199
|
+
`+`/`-` 配色,不引任何语法高亮依赖。
|
|
200
|
+
- **重命名必须同时传旧名与新名**:git 只在旧名也在 pathspec 里时才配对,只给
|
|
201
|
+
新名会把一次重命名报成整文件新增(`test/diff.mjs` 守护这一点)。
|
|
202
|
+
- **status 的 porcelain-v2 解析**:`2`(rename/copy)记录的路径在第 10 个字段、
|
|
203
|
+
与旧名以 TAB 分隔,`u`(冲突)记录的路径在第 11 个字段且状态恒为冲突——这
|
|
204
|
+
两处曾按 `slice(8)` / `slice(9)` 取值而错位,现在由 `changeEntry()` 统一构造。
|
|
205
|
+
- **插件不导入任何 `@deepseek-ai/*` 运行时包**(只用 `node:` 内置模块和
|
|
206
|
+
`@deepseek-ai/cordis`)。以 pnpm `link:` 方式安装时,宿主包无法从插件的真实
|
|
207
|
+
源码路径解析,声明这类导入会让插件在加载期就崩溃;生成所需的请求构造与流
|
|
208
|
+
式拼装因此就近实现。`test/generate.mjs` 直接测这些单元。
|
|
209
|
+
- 面板操作是普通 UI 行为(和 Cordis 面板一样),不会写入会话日志 /
|
|
210
|
+
模型提示词。AI 生成只填输入框,不会自动提交。
|
|
211
|
+
|
|
212
|
+
## 开发说明
|
|
213
|
+
|
|
214
|
+
- 浏览器 bundle 为手写(无构建步骤);改 `lib/client.js` 刷新即生效
|
|
215
|
+
(no-cache),改宿主半需要重启 `dsh web`。
|
|
216
|
+
- 测试:
|
|
217
|
+
- `node test/smoke.mjs` —— 路由/信封/端点分发/入参校验(不 spawn git:会话沙箱
|
|
218
|
+
拦截子进程管道 stdio);
|
|
219
|
+
- `node test/host-mount.mjs` —— 在真实 Cordis + 真实 `dsh-client-connection`
|
|
220
|
+
上挂载插件行(从 `DSH_HOME` 的 profile 解析 DSH 包,找不到则 SKIP);
|
|
221
|
+
- `node test/generate.mjs` —— AI 生成单元测试:路由解析、prompt 组装、截断、
|
|
222
|
+
流式拼装(block-end 与纯 delta 两条路径)、终止失败/取消/空输出;
|
|
223
|
+
- `node test/render.mjs` —— 双界面真实 React SSR 渲染,含提交区三个控件、
|
|
224
|
+
diff 解析器(行号 / 分类 / `--` 开头的删除行)、行渲染、差异面板标题栏与
|
|
225
|
+
`act()` 的结果回传/本地化(需要一份 react/react-dom,可用
|
|
226
|
+
`DSH_GIT_REACT_ROOT` 指定,找不到则 SKIP)。
|
|
227
|
+
- 也提供 `npm test`(依次跑四个)。
|
|
228
|
+
- `npm run test:commit` —— **端到端**:在临时仓库里真起 git,走插件的
|
|
229
|
+
`/dsh-git-rpc/commit` 路由提交,再用 `git log --format=%B` 逐字节比对提交
|
|
230
|
+
信息(多行、CRLF、中文、前导 `-`、shell 元字符等),并确认非法信息被拒且
|
|
231
|
+
不产生提交。
|
|
232
|
+
- `npm run test:diff` —— **端到端**:临时仓库里逐个验证 `diff` 端点:工作区
|
|
233
|
+
/ 已暂存两侧、未跟踪文件与**未跟踪目录**、**重命名配对**、删除、二进制、
|
|
234
|
+
40 万字符截断、以及路径校验与非仓库目录。
|
|
235
|
+
- 后两个都必须 spawn git 的管道 stdio,故**不在 `npm test` 内**——请在没有
|
|
236
|
+
该限制的环境(普通终端)单独运行。
|
|
237
|
+
git 命令集对照运行中的服务端做端到端验证。
|
|
238
|
+
|
|
239
|
+
## 许可
|
|
240
|
+
|
|
241
|
+
MIT,见 [LICENSE](LICENSE)。
|
package/cordis.patch.yml
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
# @xia-sc/dsh-git — browser-surface bundle layer.
|
|
2
|
-
#
|
|
3
|
-
# Applied after the dsh-web-app layer, so the `connection` service (the host
|
|
4
|
-
# half of the web transport) is already available. The host half of this
|
|
5
|
-
# package (lib/index.js) mounts the /dsh-git-rpc RPC channel that the browser
|
|
6
|
-
# half (lib/client.js, a dsh.client row) calls for the full Git workflow:
|
|
7
|
-
# status, branches, checkout, createBranch, fetch, pull, stage, commit, push,
|
|
8
|
-
# log, and AI commit-message generation.
|
|
9
|
-
#
|
|
10
|
-
# No `inject` key here on purpose: the module's own exported `inject`
|
|
11
|
-
# (webServer + connection + llm) is the declaration Cordis reads, and repeating
|
|
12
|
-
# it in the row would only create a second place to keep in sync.
|
|
13
|
-
- insert:
|
|
14
|
-
- id: dsh-git
|
|
15
|
-
name: '@xia-sc/dsh-git'
|
|
1
|
+
# @xia-sc/dsh-git — browser-surface bundle layer.
|
|
2
|
+
#
|
|
3
|
+
# Applied after the dsh-web-app layer, so the `connection` service (the host
|
|
4
|
+
# half of the web transport) is already available. The host half of this
|
|
5
|
+
# package (lib/index.js) mounts the /dsh-git-rpc RPC channel that the browser
|
|
6
|
+
# half (lib/client.js, a dsh.client row) calls for the full Git workflow:
|
|
7
|
+
# status, branches, checkout, createBranch, fetch, pull, stage, commit, push,
|
|
8
|
+
# log, and AI commit-message generation.
|
|
9
|
+
#
|
|
10
|
+
# No `inject` key here on purpose: the module's own exported `inject`
|
|
11
|
+
# (webServer + connection + llm) is the declaration Cordis reads, and repeating
|
|
12
|
+
# it in the row would only create a second place to keep in sync.
|
|
13
|
+
- insert:
|
|
14
|
+
- id: dsh-git
|
|
15
|
+
name: '@xia-sc/dsh-git'
|