gitee-ai-employee 1.0.0

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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 wangbobo-coder
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.md ADDED
@@ -0,0 +1,93 @@
1
+ # Gitee AI 员工 (gitee-ai-employee)
2
+
3
+ An issue-driven AI developer for **DeepSeek Harness**, working against **Gitee (码云)**.
4
+
5
+ Mention your bot in a Gitee issue, and the plugin clones the repository, dispatches an
6
+ AI **worker agent** to implement the change, pushes a branch, opens a Pull Request to the
7
+ branch you ask for, and can auto-merge and auto-close the issue.
8
+
9
+ > 中文说明见 [README.zh.md](README.zh.md)。
10
+
11
+ ## What it does
12
+
13
+ - **Polling**: periodically scans the Gitee issues of the repositories you watch for a new
14
+ mention of your bot (`@botName`).
15
+ - **AI development**: creates a worker agent in a profile that clones the repo (via
16
+ PowerShell + `curl` on Windows), explores it, implements the issue, runs checks, and
17
+ commits on a `ai-fix/issue-<number>` branch.
18
+ - **Branch-aware**: it reads branch instructions written in natural language and uses that
19
+ branch as both the development base and the PR target:
20
+ - `需要修改 master-sctbc 分支` / `修改 X 分支` / `改 X 分支`
21
+ - `基于 X 分支` / `在 X 分支上` / `以 X 分支为基础`
22
+ - `合并到 X` / `合到 X` / `目标分支 X` / `base: X`
23
+ The requested branch is validated against the remote with `git ls-remote`; if it does not
24
+ exist, the plugin falls back to the repository default branch and says so in a comment.
25
+ - **PR & close**: pushes the branch, creates a PR (base = the chosen branch), optionally
26
+ auto-merges it (`autoMerge`), and optionally closes the issue on success (`autoCloseIssue`).
27
+ - **Webhook / manual trigger**: optional webhook endpoint and a manual trigger URL for
28
+ instant processing without waiting for the poll.
29
+
30
+ ## Requirements
31
+
32
+ - DeepSeek Harness (dsh) 0.1.x, Windows (the plugin drives `git` and the Gitee API through
33
+ PowerShell + `C:\Windows\System32\curl.exe`), a Gitee account with a personal access token.
34
+ - The worker preset is **bundled and auto-installed** into `$DSH_HOME/.agent-presets/gitee-worker`
35
+ on first load — no manual setup.
36
+
37
+ ## Install
38
+
39
+ ```sh
40
+ dsh plugin --profile <your-profile> add gitee-ai-employee
41
+ ```
42
+
43
+ (or if you prefer installing straight from GitHub: `dsh plugin --profile <p> add github:wangbobo-coder/gitee-ai-employee`)
44
+
45
+ Restart dsh, then open **Settings → Plugins → Plugin configuration**, find the
46
+ **Gitee AI 员工** card and configure:
47
+
48
+ | Setting | Meaning |
49
+ | --- | --- |
50
+ | `giteeToken` | Gitee personal access token (kept secret; never returned by the status API) |
51
+ | `botName` | The account/issues mention that triggers the bot, e.g. `gitee-ai`. ASCII is recommended. |
52
+ | `workRoot` | Where repositories are cloned; empty = `$DSH_HOME/gitee-workers` |
53
+ | `watchRepos` | Repos to poll, one `owner/repo` per line |
54
+ | `pollEnabled` / `pollIntervalMs` | Poll on/off and interval (milliseconds) |
55
+ | `autoMerge` | Auto-merge the created PR |
56
+ | `autoCloseIssue` | Close the issue once the task succeeds |
57
+ | `workerPreset` | Agent preset id used for the worker (default `gitee-worker`) |
58
+
59
+ ## Usage
60
+
61
+ 1. Make sure `pollEnabled` is on (or use the manual trigger URL shown in the status API).
62
+ 2. Open (or edit) an issue in a watched repo and mention your bot:
63
+ ```
64
+ @gitee-ai
65
+ 需要修改 master-sctbc 分支
66
+ 参会人员选择增加学生选项,注意回显处理。
67
+ ```
68
+ 3. The plugin picks it up (usually within one poll interval), comments "已接单", runs the
69
+ worker, and later posts the result with the PR link. With `autoCloseIssue` on, the issue
70
+ is closed on success.
71
+
72
+ > The bot name match uses a lookahead (`(?![A-Za-z0-9_])`) so both ASCII and CJK bot names
73
+ > trigger correctly.
74
+
75
+ ## Security notes
76
+
77
+ - User configuration is persisted to the **user's own profile patch layer**; the shipped
78
+ `cordis.patch.yml` contains only an empty default and never a token.
79
+ - The token is marked secret in the schema and is never echoed by the status/config API
80
+ (only a `tokenConfigured` boolean).
81
+ - The plugin runs with the same privileges as your dsh process; its worker agent gets
82
+ `danger-full-access` inside the cloned workspace only.
83
+
84
+ ## Development
85
+
86
+ - `dsh/index.js` — host plugin (Cordis object plugin with `dsh.bundle` manifest).
87
+ - `dsh/client.js` — web client half (settings card hover/status).
88
+ - `preset/gitee-worker/` — the bundled worker agent preset, auto-copied to
89
+ `$DSH_HOME/.agent-presets/gitee-worker` on load if missing.
90
+
91
+ ## License
92
+
93
+ MIT
package/README.zh.md ADDED
@@ -0,0 +1,80 @@
1
+ # Gitee AI 员工 (gitee-ai-employee)
2
+
3
+ 面向 **DeepSeek Harness** 的 issue 驱动型 AI 开发者,对接 **Gitee(码云)**。
4
+
5
+ 在 Gitee issue 里 @ 你的机器人,插件就会克隆仓库、派出 AI **worker agent** 实现改动、
6
+ 推送分支、按你指定的分支创建 Pull Request,并可按需自动合并、自动关闭 issue。
7
+
8
+ ## 功能
9
+
10
+ - **轮询**:定时扫描你监听的仓库里新出现 `@botName` 的 issue。
11
+ - **AI 开发**:创建 worker agent(Windows 下经 PowerShell + curl 克隆仓库),探索代码、
12
+ 实现需求、跑校验、在 `ai-fix/issue-<number>` 分支上提交。
13
+ - **分支感知**:会读取 issue 里用自然语言指定的分支,作为开发基准和 PR 目标:
14
+ - `需要修改 master-sctbc 分支` / `修改 X 分支` / `改 X 分支`
15
+ - `基于 X 分支` / `在 X 分支上` / `以 X 分支为基础`
16
+ - `合并到 X` / `合到 X` / `目标分支 X` / `base: X`
17
+ 指定分支会用 `git ls-remote` 校验远端存在性;不存在则退回仓库默认分支并在评论中说明。
18
+ - **PR 与关闭**:推送分支、创建 PR(base = 所选分支),可按需自动合并(`autoMerge`)、
19
+ 成功后自动关闭 issue(`autoCloseIssue`)。
20
+ - **Webhook / 手动触发**:可选 webhook 端点与手动触发 URL,无需等轮询即时处理。
21
+
22
+ ## 环境要求
23
+
24
+ - DeepSeek Harness (dsh) 0.1.x,Windows(插件通过 PowerShell + `C:\Windows\System32\curl.exe`
25
+ 驱动 git 与 Gitee API),Gitee 账号及个人访问令牌。
26
+ - worker 预设在包内**自带并在首次加载时自动安装**到 `$DSH_HOME/.agent-presets/gitee-worker`,
27
+ 无需手动配置。
28
+
29
+ ## 安装
30
+
31
+ ```sh
32
+ dsh plugin --profile <你的-profile> add gitee-ai-employee
33
+ ```
34
+
35
+ (或直接从 GitHub 安装:`dsh plugin --profile <p> add github:wangbobo-coder/gitee-ai-employee`)
36
+
37
+ 重启 dsh 后,打开 **设置 → 插件 → 插件配置**,找到 **Gitee AI 员工** 卡片进行配置:
38
+
39
+ | 配置项 | 说明 |
40
+ | --- | --- |
41
+ | `giteeToken` | Gitee 个人访问令牌(标记为 secret,状态接口不回显) |
42
+ | `botName` | 触发机器人的账号,如 `gitee-ai`。建议使用 ASCII 名称。 |
43
+ | `workRoot` | 仓库克隆目录;留空 = `$DSH_HOME/gitee-workers` |
44
+ | `watchRepos` | 监听仓库,每行一个 `owner/repo` |
45
+ | `pollEnabled` / `pollIntervalMs` | 是否轮询 / 轮询间隔(毫秒) |
46
+ | `autoMerge` | 是否自动合并创建的 PR |
47
+ | `autoCloseIssue` | 任务成功后是否自动关闭 issue |
48
+ | `workerPreset` | worker 使用的 agent 预设 id(默认 `gitee-worker`) |
49
+
50
+ ## 使用
51
+
52
+ 1. 打开 `pollEnabled`(或使用状态接口给出的手动触发 URL)。
53
+ 2. 在监听的仓库里新建/编辑 issue 并 @ 机器人:
54
+ ```
55
+ @gitee-ai
56
+ 需要修改 master-sctbc 分支
57
+ 参会人员选择增加学生选项,注意回显处理。
58
+ ```
59
+ 3. 插件会在一个轮询周期内接单(评论“已接单”),运行 worker,最后回帖结果与 PR 链接;
60
+ 开启 `autoCloseIssue` 时成功后自动关闭 issue。
61
+
62
+ > 机器人匹配使用前瞻断言(`(?![A-Za-z0-9_])`),ASCII 与中文 botName 都能正确触发。
63
+
64
+ ## 安全说明
65
+
66
+ - 用户配置持久化在**用户自身 profile 的 patch 层**;随包分发的 `cordis.patch.yml`
67
+ 只含空默认,绝不包含 token。
68
+ - token 在 schema 中标记为 secret,状态/配置接口只返回 `tokenConfigured` 布尔值,不回显明文。
69
+ - 插件与你 dsh 进程同权限运行;worker agent 仅在克隆的工作区内获得 `danger-full-access`。
70
+
71
+ ## 开发
72
+
73
+ - `dsh/index.js` — 宿主插件(标准 Cordis 对象插件,声明 `dsh.bundle` manifest)。
74
+ - `dsh/client.js` — Web 客户端半体(设置卡片/状态展示)。
75
+ - `preset/gitee-worker/` — 内置 worker agent 预设,首次加载缺失时自动复制到
76
+ `$DSH_HOME/.agent-presets/gitee-worker`。
77
+
78
+ ## License
79
+
80
+ MIT
@@ -0,0 +1,9 @@
1
+ # Gitee AI 员工 —— 默认配置层(bundle patch)。
2
+ #
3
+ # 注意:本文件只含插件行的插入声明(无 config 块 = 全部使用代码内置的
4
+ # 空默认值),绝不携带任何隐私信息或 token。
5
+ # 用户配置通过「设置 → 插件配置」卡片保存,持久化在用户自身 profile
6
+ # 层的 patch 中;本文件始终保持干净,可安全随包分发。
7
+ - insert:
8
+ - id: gitee-ai-employee
9
+ name: gitee-ai-employee
package/dsh/client.js ADDED
@@ -0,0 +1,424 @@
1
+ // Browser half of the Gitee AI employee dsh plugin: a settings card in the
2
+ // installed-plugins list (设置 → 已安装插件). The card reuses the exact
3
+ // PluginCard.module.css class names injected by @deepseek-ai/dsh-client-ui
4
+ // -settings-plugins (YyYd_a_*), so it renders pixel-identical to the built-in
5
+ // Shell / Agent loop / Web search cards — a border, a 12px radius, a big bold
6
+ // name header with a description, a collapsing body of fields, and a
7
+ // discard/save footer.
8
+ //
9
+ // Hand-written in the lazy-CJS bundle protocol (window.__ModuleLoader__.load
10
+ // with a factory returning cordis-plugin exports), so no build step and no
11
+ // imports from dsh client packages — the same zero-dependency stance as the
12
+ // host half. `slots` is optional (registered lazily via ctx.inject), so a
13
+ // non-web profile simply never renders the card.
14
+ window.__ModuleLoader__.load({
15
+ id: 'gitee-ai-employee',
16
+ factory: (require) => {
17
+ var module = { exports: {} }
18
+ var exports = module.exports
19
+
20
+ function labels() {
21
+ return {
22
+ title: 'Gitee AI 员工',
23
+ description: 'issue @员工 → AI 开发 → PR → 自动关闭',
24
+ expand: '展开',
25
+ collapse: '收起',
26
+ unsaved: '未保存',
27
+ loading: '加载中…',
28
+ loadFailed: '配置加载失败',
29
+ discard: '放弃',
30
+ save: '保存',
31
+ saving: '保存中…',
32
+ savedDone: '保存完成,已生效',
33
+ readOnly: '只读',
34
+ token: 'Gitee 私人令牌',
35
+ tokenHint: '留空保持原值',
36
+ bot: '机器人账号',
37
+ botHint: 'issue 里 @ 它才触发',
38
+ workRoot: '仓库工作目录',
39
+ repos: '监听仓库',
40
+ reposHint: '每行一个 owner/repo',
41
+ poll: '启用定时轮询',
42
+ interval: '轮询间隔(毫秒)',
43
+ merge: '自动合并 PR',
44
+ close: '成功后自动关闭 issue',
45
+ status: '查看任务状态',
46
+ }
47
+ }
48
+
49
+ function ConfigCard(react, ui) {
50
+ var h = react.createElement
51
+ var Input = ui && ui.Input
52
+ var Chevron = ui && ui.IconChevronDownOutline14
53
+
54
+ // 复用内置 settings-plugins 注入的字段样式类(At1oFq_*)
55
+ var fieldRow = (label, hint, control, key, groupName) =>
56
+ h(
57
+ groupName ? 'div' : 'label',
58
+ {
59
+ key: key,
60
+ role: groupName ? 'group' : undefined,
61
+ 'aria-label': groupName || undefined,
62
+ className: 'At1oFq_field',
63
+ style: { display: 'flex', flexDirection: 'column', gap: '6px', padding: '12px 0' },
64
+ },
65
+ h('div', { className: 'At1oFq_head' },
66
+ h('span', { className: 'At1oFq_label' }, label),
67
+ hint
68
+ ? h('span', { className: 'At1oFq_badgeMuted', style: { fontSize: '11px', lineHeight: '17px', color: 'var(--dsw-alias-label-tertiary)' } }, hint)
69
+ : null,
70
+ ),
71
+ control,
72
+ )
73
+
74
+ // 自定义开关行:左侧标签,右侧紧凑的 iOS 风格 toggle
75
+ var toggleRow = (label, hint, checked, onChange, key) =>
76
+ h(
77
+ 'div',
78
+ {
79
+ key: key,
80
+ className: 'At1oFq_field',
81
+ style: { display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: '12px', padding: '10px 0' },
82
+ },
83
+ h('div', { style: { minWidth: 0, display: 'flex', flexDirection: 'column', gap: '2px' } },
84
+ h('span', {
85
+ className: 'At1oFq_label',
86
+ style: { margin: 0, fontSize: '13px', fontWeight: 500, color: 'var(--dsw-alias-label-primary)' },
87
+ }, label),
88
+ hint
89
+ ? h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)', lineHeight: 1.4 } }, hint)
90
+ : null,
91
+ ),
92
+ h(Switch, { checked: !!checked, onChange, label }),
93
+ )
94
+
95
+ // 紧凑圆形开关(轨道 + 滑块),用 DSH 主题 token 着色
96
+ function Switch(props) {
97
+ var on = !!props.checked
98
+ return h(
99
+ 'button',
100
+ {
101
+ type: 'button',
102
+ role: 'switch',
103
+ 'aria-checked': on,
104
+ 'aria-label': props.label,
105
+ onClick: () => props.onChange(!on),
106
+ style: {
107
+ appearance: 'none',
108
+ flex: 'none',
109
+ width: '36px',
110
+ height: '20px',
111
+ borderRadius: '999px',
112
+ border: 0,
113
+ padding: 0,
114
+ cursor: 'pointer',
115
+ position: 'relative',
116
+ background: on
117
+ ? 'var(--dsw-alias-brand-primary, #2563eb)'
118
+ : 'var(--dsw-alias-bg-module-platform, rgba(127,127,127,0.35))',
119
+ transition: 'background .16s',
120
+ outline: 'none',
121
+ boxShadow: 'inset 0 0 0 1px rgba(127,127,127,0.25)',
122
+ },
123
+ },
124
+ h('span', {
125
+ style: {
126
+ position: 'absolute',
127
+ top: '2px',
128
+ left: on ? '18px' : '2px',
129
+ width: '16px',
130
+ height: '16px',
131
+ borderRadius: '999px',
132
+ background: '#fff',
133
+ boxShadow: '0 1px 2px rgba(0,0,0,0.35)',
134
+ transition: 'left .16s',
135
+ },
136
+ }),
137
+ )
138
+ }
139
+
140
+ // 平衡:onChange 里用 ref 读最新 draft,避免闭包陈旧
141
+ var draftRef = { current: null }
142
+
143
+ return function GiteeCard() {
144
+ var t = labels()
145
+ var openState = react.useState(false)
146
+ var summaryState = react.useState(null)
147
+ var draftState = react.useState(null)
148
+ var busyState = react.useState(false)
149
+ var noteState = react.useState('')
150
+ var failedState = react.useState(false)
151
+ var dirtyState = react.useState(false)
152
+ var open = openState[0]
153
+ var summary = summaryState[0]
154
+ var draft = draftState[0]
155
+ var busy = busyState[0]
156
+ var note = noteState[0]
157
+ var failed = failedState[0]
158
+ var dirty = dirtyState[0]
159
+
160
+ draftRef.current = draft
161
+
162
+ var seedDraft = (config) => ({
163
+ botName: (config && config.botName) || '',
164
+ workRoot: (config && config.workRoot) || '',
165
+ watchRepos: ((config && config.watchRepos) || []).join('\n'),
166
+ pollEnabled: !!(config && config.pollEnabled),
167
+ pollIntervalMs: (config && config.pollIntervalMs) || 60000,
168
+ autoMerge: !!(config && config.autoMerge),
169
+ autoCloseIssue: !!(config && config.autoCloseIssue),
170
+ tokenConfigured: !!(config && config.tokenConfigured),
171
+ })
172
+
173
+ var load = react.useCallback(() => {
174
+ fetch('/gitee-ai/config')
175
+ .then((r) => r.json().then((body) => {
176
+ if (!r.ok) throw new Error(body.error || 'load failed')
177
+ return body
178
+ }))
179
+ .then((body) => {
180
+ summaryState[1](body.config)
181
+ var d = seedDraft(body.config)
182
+ draftRef.current = d
183
+ draftState[1](d)
184
+ noteState[1]('')
185
+ failedState[1](false)
186
+ })
187
+ .catch((error) => {
188
+ noteState[1](String(error.message ? error.message : error))
189
+ failedState[1](true)
190
+ })
191
+ }, [])
192
+
193
+ react.useEffect(() => {
194
+ if (open && summary === null) load()
195
+ }, [open, summary, load])
196
+
197
+ var setDraft = (d) => { draftState[1](d); dirtyState[1](true) }
198
+ var setDraftFromControl = (patch) => {
199
+ var d = Object.assign({}, draftRef.current, patch)
200
+ draftRef.current = d
201
+ draftState[1](d)
202
+ dirtyState[1](true)
203
+ }
204
+
205
+ var save = () => {
206
+ if (!draft) return
207
+ busyState[1](true)
208
+ failedState[1](false)
209
+ noteState[1]('')
210
+ var payload = {
211
+ botName: draft.botName,
212
+ workRoot: draft.workRoot,
213
+ watchRepos: (draft.watchRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean),
214
+ pollEnabled: draft.pollEnabled,
215
+ pollIntervalMs: Number(draft.pollIntervalMs) || 60000,
216
+ autoMerge: draft.autoMerge,
217
+ autoCloseIssue: draft.autoCloseIssue,
218
+ }
219
+ fetch('/gitee-ai/config', {
220
+ method: 'POST',
221
+ headers: { 'Content-Type': 'application/json' },
222
+ body: JSON.stringify(payload),
223
+ })
224
+ .then((r) => r.json().then((body) => {
225
+ if (!r.ok) throw new Error(body.error || 'save failed')
226
+ return body
227
+ }))
228
+ .then((body) => {
229
+ noteState[1](t.savedDone)
230
+ summaryState[1](body.config)
231
+ var d = seedDraft(body.config)
232
+ draftRef.current = d
233
+ draftState[1](d)
234
+ dirtyState[1](false)
235
+ })
236
+ .catch((error) => {
237
+ noteState[1](String(error.message ? error.message : error))
238
+ failedState[1](true)
239
+ })
240
+ .finally(() => busyState[1](false))
241
+ }
242
+
243
+ var discard = () => {
244
+ if (!summary) return
245
+ var d = seedDraft(summary)
246
+ draftRef.current = d
247
+ draftState[1](d)
248
+ dirtyState[1](false)
249
+ noteState[1]('')
250
+ failedState[1](false)
251
+ }
252
+
253
+ // ── 头部(大标题 + 描述 + 展开箭头)──
254
+ var statusLine = summary
255
+ ? '轮询' + (summary.pollEnabled ? ' 开 · ' + Math.round((summary.pollIntervalMs || 0) / 1000) + 's' : ' 关')
256
+ + ' · 仓库 ' + ((summary.watchRepos || []).length) + ' 个'
257
+ + (summary.tokenConfigured ? '' : ' · 未配置令牌')
258
+ : t.description
259
+
260
+ var chevronEl = Chevron
261
+ ? h(Chevron, {
262
+ className: 'YyYd_a_chevron' + (open ? ' YyYd_a_chevronOpen' : ''),
263
+ })
264
+ : h('span', {
265
+ className: 'YyYd_a_chevron' + (open ? ' YyYd_a_chevronOpen' : ''),
266
+ style: { display: 'inline-flex' },
267
+ }, '▾')
268
+
269
+ var header = h(
270
+ 'button',
271
+ {
272
+ type: 'button',
273
+ className: 'YyYd_a_header',
274
+ 'aria-expanded': open,
275
+ 'aria-label': (open ? t.collapse : t.expand) + ': ' + t.title,
276
+ onClick: () => openState[1](!open),
277
+ },
278
+ h('span', { className: 'YyYd_a_headText' },
279
+ h('span', { className: 'YyYd_a_name' }, t.title),
280
+ h('span', { className: 'YyYd_a_description' }, statusLine),
281
+ ),
282
+ dirty ? h('span', { className: 'YyYd_a_pending' }, t.unsaved) : null,
283
+ chevronEl,
284
+ )
285
+
286
+ // ── 主体(字段 + 底部保存)──
287
+ var body = null
288
+ if (open) {
289
+ if (!summary || !draft) {
290
+ body = h(
291
+ 'div',
292
+ { className: 'YyYd_a_body', style: { padding: '12px 16px', color: 'var(--dsw-alias-label-tertiary)', fontSize: '13px' } },
293
+ note || t.loading,
294
+ )
295
+ } else {
296
+ var controls = []
297
+ controls.push(fieldRow(t.token, draft.tokenConfigured ? t.tokenHint : undefined, h(Input, {
298
+ key: 'token',
299
+ type: 'password',
300
+ placeholder: draft.tokenConfigured ? '••••••••' : '输入 Gitee 私人令牌',
301
+ value: (draft.token !== undefined ? draft.token : ''),
302
+ onChange: (e) => setDraftFromControl({ token: e.target.value }),
303
+ }), 'token'))
304
+ controls.push(fieldRow(t.bot, t.botHint, h(Input, {
305
+ key: 'bot',
306
+ value: draft.botName || '',
307
+ onChange: (e) => setDraftFromControl({ botName: e.target.value }),
308
+ }), 'bot'))
309
+ controls.push(fieldRow(t.workRoot, undefined, h(Input, {
310
+ key: 'workRoot',
311
+ value: draft.workRoot || '',
312
+ onChange: (e) => setDraftFromControl({ workRoot: e.target.value }),
313
+ }), 'workRoot'))
314
+ controls.push(fieldRow(t.repos, t.reposHint, h('textarea', {
315
+ key: 'repos',
316
+ rows: 3,
317
+ className: 'At1oFq_input',
318
+ style: { height: 'auto', padding: '8px 12px', resize: 'vertical', lineHeight: 1.5 },
319
+ value: draft.watchRepos || '',
320
+ onChange: (e) => setDraftFromControl({ watchRepos: e.target.value }),
321
+ }), 'repos'))
322
+ controls.push(toggleRow(t.poll, undefined, draft.pollEnabled, (v) => setDraftFromControl({ pollEnabled: v }), 'poll'))
323
+ controls.push(fieldRow(t.interval, undefined, h(Input, {
324
+ key: 'interval',
325
+ type: 'number',
326
+ value: draft.pollIntervalMs,
327
+ onChange: (e) => setDraftFromControl({ pollIntervalMs: e.target.value }),
328
+ }), 'interval'))
329
+ controls.push(toggleRow(t.merge, undefined, draft.autoMerge, (v) => setDraftFromControl({ autoMerge: v }), 'merge'))
330
+ controls.push(toggleRow(t.close, undefined, draft.autoCloseIssue, (v) => setDraftFromControl({ autoCloseIssue: v }), 'close'))
331
+
332
+ body = h(
333
+ 'div',
334
+ { className: 'YyYd_a_body' },
335
+ h('a', {
336
+ href: '/gitee-ai/settings',
337
+ target: '_blank',
338
+ rel: 'noreferrer',
339
+ style: { display: 'inline-block', marginTop: '12px', fontSize: '12px', color: 'var(--dsw-alias-label-secondary)', textDecoration: 'underline' },
340
+ }, t.status + ' ↗'),
341
+ controls,
342
+ note
343
+ ? h('p', {
344
+ className: failed ? 'YyYd_a_failed' : 'YyYd_a_readOnly',
345
+ role: 'status',
346
+ style: failed ? {} : { margin: '8px 0 0', fontSize: '12px', lineHeight: '1.5' },
347
+ }, note)
348
+ : null,
349
+ h('div', { className: 'YyYd_a_footer' },
350
+ h('button', {
351
+ type: 'button',
352
+ className: 'YyYd_a_discard',
353
+ disabled: !dirty || busy,
354
+ onClick: discard,
355
+ }, t.discard),
356
+ h('button', {
357
+ type: 'button',
358
+ className: 'YyYd_a_save',
359
+ disabled: busy,
360
+ onClick: save,
361
+ }, busy ? t.saving : t.save),
362
+ ),
363
+ )
364
+ }
365
+ }
366
+
367
+ return h(
368
+ 'li',
369
+ { className: 'YyYd_a_card' + (open ? ' YyYd_a_cardOpen' : '') },
370
+ header,
371
+ body,
372
+ )
373
+ }
374
+ }
375
+
376
+ function registerCard(ctx) {
377
+ if (typeof ctx.inject !== 'function') return
378
+ ctx.inject(['slots'], (scope) => {
379
+ fetch('/gitee-ai/config')
380
+ .then((response) => {
381
+ if (response.status === 404) return
382
+ try {
383
+ mountCard(scope)
384
+ } catch (error) {
385
+ console.error('[gitee-ai] settings card skipped: ' + error)
386
+ }
387
+ })
388
+ .catch(() => {})
389
+ })
390
+ }
391
+
392
+ function mountCard(ctx) {
393
+ var react
394
+ try {
395
+ react = require('react')
396
+ } catch (error) {
397
+ console.error('[gitee-ai] settings card skipped: ' + error)
398
+ return
399
+ }
400
+ var ui
401
+ try {
402
+ ui = require('@deepseek-ai/dsh-client-ui-primitives')
403
+ } catch (error) {
404
+ ui = {}
405
+ }
406
+ var Card = ConfigCard(react, ui)
407
+ ctx.slots.inject('settings.plugin.item', function* () {
408
+ yield ctx.slots.register({ name: 'settings.plugin.item', id: 'gitee-ai-employee', key: 'gitee-ai-employee', order: 30 }, Card)
409
+ })
410
+ }
411
+
412
+ function apply(ctx) {
413
+ registerCard(ctx)
414
+ if (typeof ctx.effect === 'function') {
415
+ ctx.effect(() => () => {}, 'gitee-ai: settings card')
416
+ }
417
+ }
418
+
419
+ exports.apply = apply
420
+ // `slots` is optional, so it is not required here: registerCard checks.
421
+ exports.inject = []
422
+ return module.exports
423
+ },
424
+ })