gitee-ai-employee 1.2.7 → 1.2.8

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.
Files changed (3) hide show
  1. package/dsh/client.js +660 -660
  2. package/dsh/index.js +12 -1
  3. package/package.json +1 -1
package/dsh/client.js CHANGED
@@ -1,660 +1,660 @@
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
- common: '连接与公共配置',
47
- issueBlock: '处理 Issue(自动开发 · 提 PR)',
48
- scan: '代码安全扫描',
49
- scanEnable: '启用定时扫描(全部仓库排队持续扫描,去重幂等)',
50
- scanReposLabel: '扫描仓库',
51
- scanReposHint: '每行一个 owner/repo 或完整克隆地址(https://gitee.com/owner/repo.git、git@github.com:owner/repo.git 均可),[gitee:|github:] 前缀可选',
52
- scanSeverity: '最低上报级别',
53
- scanSeverityHint: 'critical / high / medium / low / none',
54
- scanOneIssue: '一次扫描合并为一个 issue',
55
- scanConcurrency: '同时扫描仓库数(1~8,默认 3,自动排队)',
56
- scanPromptsLabel: '自定义提示词(JSON)',
57
- scanPromptsHint: '{"id":{"name":"名称","prompt":"内容"}},同 id 覆盖内置,留空用内置',
58
- scanInterval: '扫描间隔(毫秒)',
59
- scanFetch: '一键获取我的仓库',
60
- scanFetching: '获取中…',
61
- scanNow: '立即扫描全部仓库',
62
- scanning: '触发中…',
63
- scanDone: '已触发,结果见 /gitee-ai/scan',
64
- scanFailed: '触发失败',
65
- scanNeedRepos: '请先填写扫描仓库',
66
- }
67
- }
68
-
69
- function ConfigCard(react, ui) {
70
- var h = react.createElement
71
- var Input = ui && ui.Input
72
- var Chevron = ui && ui.IconChevronDownOutline14
73
-
74
- // 复用内置 settings-plugins 注入的字段样式类(At1oFq_*)
75
- var fieldRow = (label, hint, control, key, groupName) =>
76
- h(
77
- groupName ? 'div' : 'label',
78
- {
79
- key: key,
80
- role: groupName ? 'group' : undefined,
81
- 'aria-label': groupName || undefined,
82
- className: 'At1oFq_field',
83
- style: { display: 'flex', flexDirection: 'column', gap: '6px', padding: '12px 0' },
84
- },
85
- h('div', { className: 'At1oFq_head' },
86
- h('span', { className: 'At1oFq_label' }, label),
87
- ),
88
- hint
89
- ? h('div', { style: { fontSize: '12px', lineHeight: '17px', color: 'var(--dsw-alias-label-tertiary)', display: 'block', whiteSpace: 'normal', overflow: 'visible', textOverflow: 'clip' } }, hint)
90
- : null,
91
- control,
92
- )
93
-
94
- // 分区标题块:色条 + 标题 + 行为标签 + 说明,
95
- // 用于把「处理 Issue」与「代码安全扫描」两个功能模块清楚分隔,防止误配置/误操作。
96
- var sectionBlock = (label, sub, tag, tagBg, accent, key) =>
97
- h('div', {
98
- key: key,
99
- style: {
100
- margin: '16px 0 6px',
101
- padding: '10px 12px',
102
- borderRadius: '10px',
103
- border: '1px solid ' + accent,
104
- background: 'linear-gradient(90deg, ' + accent + '1A, transparent 75%)',
105
- display: 'flex', flexDirection: 'column', gap: '4px',
106
- },
107
- },
108
- h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' } },
109
- h('span', { style: { width: '6px', height: '16px', borderRadius: '3px', background: accent, display: 'inline-block' } }),
110
- h('span', { style: { fontSize: '14px', fontWeight: 800, color: 'var(--dsw-alias-label-primary)' } }, label),
111
- tag ? h('span', { style: { fontSize: '11px', fontWeight: 700, color: '#fff', background: tagBg, borderRadius: '999px', padding: '2px 8px' } }, tag) : null,
112
- ),
113
- sub ? h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)', lineHeight: 1.45 } }, sub) : null,
114
- )
115
-
116
- // 分区内层卡片:把某一模块的全部字段包进带浅色描边的容器,与另一模块形成视觉隔离。
117
- var innerCard = (key, accent, children) =>
118
- h('div', {
119
- key: key,
120
- style: {
121
- border: '1px solid ' + accent + '59',
122
- borderRadius: '10px',
123
- padding: '2px 14px 6px',
124
- margin: '2px 0 2px',
125
- background: accent + '0D',
126
- },
127
- }, children)
128
-
129
- // 自定义开关行:左侧标签,右侧紧凑的 iOS 风格 toggle
130
- var toggleRow = (label, hint, checked, onChange, key) =>
131
- h(
132
- 'div',
133
- {
134
- key: key,
135
- className: 'At1oFq_field',
136
- style: { display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: '12px', padding: '10px 0' },
137
- },
138
- h('div', { style: { minWidth: 0, display: 'flex', flexDirection: 'column', gap: '2px' } },
139
- h('span', {
140
- className: 'At1oFq_label',
141
- style: { margin: 0, fontSize: '13px', fontWeight: 500, color: 'var(--dsw-alias-label-primary)' },
142
- }, label),
143
- hint
144
- ? h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)', lineHeight: 1.4 } }, hint)
145
- : null,
146
- ),
147
- h(Switch, { checked: !!checked, onChange, label }),
148
- )
149
-
150
- // 紧凑圆形开关(轨道 + 滑块),用 DSH 主题 token 着色
151
- function Switch(props) {
152
- var on = !!props.checked
153
- return h(
154
- 'button',
155
- {
156
- type: 'button',
157
- role: 'switch',
158
- 'aria-checked': on,
159
- 'aria-label': props.label,
160
- onClick: () => props.onChange(!on),
161
- style: {
162
- appearance: 'none',
163
- flex: 'none',
164
- width: '36px',
165
- height: '20px',
166
- borderRadius: '999px',
167
- border: 0,
168
- padding: 0,
169
- cursor: 'pointer',
170
- position: 'relative',
171
- background: on
172
- ? 'var(--dsw-alias-brand-primary, #2563eb)'
173
- : 'var(--dsw-alias-bg-module-platform, rgba(127,127,127,0.35))',
174
- transition: 'background .16s',
175
- outline: 'none',
176
- boxShadow: 'inset 0 0 0 1px rgba(127,127,127,0.25)',
177
- },
178
- },
179
- h('span', {
180
- style: {
181
- position: 'absolute',
182
- top: '2px',
183
- left: on ? '18px' : '2px',
184
- width: '16px',
185
- height: '16px',
186
- borderRadius: '999px',
187
- background: '#fff',
188
- boxShadow: '0 1px 2px rgba(0,0,0,0.35)',
189
- transition: 'left .16s',
190
- },
191
- }),
192
- )
193
- }
194
-
195
- // 平衡:onChange 里用 ref 读最新 draft,避免闭包陈旧
196
- var draftRef = { current: null }
197
-
198
- return function GiteeCard() {
199
- var t = labels()
200
- var openState = react.useState(false)
201
- var summaryState = react.useState(null)
202
- var draftState = react.useState(null)
203
- var busyState = react.useState(false)
204
- var noteState = react.useState('')
205
- var failedState = react.useState(false)
206
- var dirtyState = react.useState(false)
207
- var open = openState[0]
208
- var summary = summaryState[0]
209
- var draft = draftState[0]
210
- var busy = busyState[0]
211
- var note = noteState[0]
212
- var failed = failedState[0]
213
- var dirty = dirtyState[0]
214
- var scanBusyState = react.useState(false)
215
- var scanNoteState = react.useState('')
216
- var scanBusy = scanBusyState[0]
217
- var scanNote = scanNoteState[0]
218
- var fetchBusyState = react.useState(false)
219
- var fetchNoteState = react.useState('')
220
- var fetchBusy = fetchBusyState[0]
221
- var fetchNote = fetchNoteState[0]
222
-
223
- draftRef.current = draft
224
-
225
- var seedDraft = (config) => ({
226
- botName: (config && config.botName) || '',
227
- workRoot: (config && config.workRoot) || '',
228
- watchRepos: ((config && config.watchRepos) || []).join('\n'),
229
- pollEnabled: !!(config && config.pollEnabled),
230
- pollIntervalMs: (config && config.pollIntervalMs) || 60000,
231
- autoMerge: !!(config && config.autoMerge),
232
- autoCloseIssue: !!(config && config.autoCloseIssue),
233
- tokenConfigured: !!(config && config.tokenConfigured),
234
- githubTokenConfigured: !!(config && config.githubTokenConfigured),
235
- scanEnabled: !!(config && config.scanEnabled),
236
- scanRepos: ((config && config.scanRepos) || []).join('\n'),
237
- scanMinSeverity: (config && config.scanMinSeverity) || 'medium',
238
- scanOneIssuePerRun: (config && config.scanOneIssuePerRun) !== false,
239
- scanPrompts: (config && config.scanPrompts && Object.keys(config.scanPrompts).length)
240
- ? JSON.stringify(config.scanPrompts, null, 2) : '',
241
- scanIntervalMs: (config && config.scanIntervalMs) || 21600000,
242
- scanConcurrency: (config && config.scanConcurrency) || 3,
243
- })
244
-
245
- var load = react.useCallback(() => {
246
- fetch('/gitee-ai/config')
247
- .then((r) => r.json().then((body) => {
248
- if (!r.ok) throw new Error(body.error || 'load failed')
249
- return body
250
- }))
251
- .then((body) => {
252
- summaryState[1](body.config)
253
- var d = seedDraft(body.config)
254
- draftRef.current = d
255
- draftState[1](d)
256
- noteState[1]('')
257
- failedState[1](false)
258
- })
259
- .catch((error) => {
260
- noteState[1](String(error.message ? error.message : error))
261
- failedState[1](true)
262
- })
263
- }, [])
264
-
265
- react.useEffect(() => {
266
- if (open && summary === null) load()
267
- }, [open, summary, load])
268
-
269
- var setDraft = (d) => { draftState[1](d); dirtyState[1](true) }
270
- var setDraftFromControl = (patch) => {
271
- var d = Object.assign({}, draftRef.current, patch)
272
- draftRef.current = d
273
- draftState[1](d)
274
- dirtyState[1](true)
275
- }
276
-
277
- var save = () => {
278
- if (!draft) return
279
- var prompts = {}
280
- if (draft.scanPrompts && draft.scanPrompts.trim()) {
281
- try {
282
- var parsed = JSON.parse(draft.scanPrompts)
283
- if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
284
- for (var id in parsed) {
285
- var pv = parsed[id] || {}
286
- var nm = String(pv.name || '').trim()
287
- var pt = String(pv.prompt || '').trim()
288
- if (nm && pt) prompts[id] = { name: nm, prompt: pt }
289
- }
290
- }
291
- } catch (e) {
292
- noteState[1]('提示词 JSON 格式错误,请检查')
293
- failedState[1](true)
294
- return
295
- }
296
- }
297
- busyState[1](true)
298
- failedState[1](false)
299
- noteState[1]('')
300
- var payload = {
301
- ...((draft.token && draft.token.trim()) ? { giteeToken: draft.token.trim() } : {}),
302
- ...((draft.githubToken && draft.githubToken.trim()) ? { githubToken: draft.githubToken.trim() } : {}),
303
- botName: draft.botName,
304
- workRoot: draft.workRoot,
305
- watchRepos: (draft.watchRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean),
306
- pollEnabled: draft.pollEnabled,
307
- pollIntervalMs: Number(draft.pollIntervalMs) || 60000,
308
- autoMerge: draft.autoMerge,
309
- autoCloseIssue: draft.autoCloseIssue,
310
- scanEnabled: !!draft.scanEnabled,
311
- scanRepos: (draft.scanRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean),
312
- scanMinSeverity: draft.scanMinSeverity || 'medium',
313
- scanOneIssuePerRun: draft.scanOneIssuePerRun !== false,
314
- scanPrompts: prompts,
315
- scanIntervalMs: Number(draft.scanIntervalMs) || 21600000,
316
- scanConcurrency: Number(draft.scanConcurrency) || 3,
317
- }
318
- fetch('/gitee-ai/config', {
319
- method: 'POST',
320
- headers: { 'Content-Type': 'application/json' },
321
- body: JSON.stringify(payload),
322
- })
323
- .then((r) => r.json().then((body) => {
324
- if (!r.ok) throw new Error(body.error || 'save failed')
325
- return body
326
- }))
327
- .then((body) => {
328
- noteState[1](t.savedDone)
329
- summaryState[1](body.config)
330
- var d = seedDraft(body.config)
331
- draftRef.current = d
332
- draftState[1](d)
333
- dirtyState[1](false)
334
- })
335
- .catch((error) => {
336
- noteState[1](String(error.message ? error.message : error))
337
- failedState[1](true)
338
- })
339
- .finally(() => busyState[1](false))
340
- }
341
-
342
- var discard = () => {
343
- if (!summary) return
344
- var d = seedDraft(summary)
345
- draftRef.current = d
346
- draftState[1](d)
347
- dirtyState[1](false)
348
- noteState[1]('')
349
- failedState[1](false)
350
- }
351
-
352
- // 立即扫描:对 draft 里的 scanRepos 逐个 POST /gitee-ai/scan(服务端自动排队,按并发上限持续扫描)
353
- var scanNow = () => {
354
- if (!draft) return
355
- var repos = (draft.scanRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean)
356
- if (!repos.length) { scanNoteState[1](t.scanNeedRepos); return }
357
- scanBusyState[1](true)
358
- scanNoteState[1]('')
359
- var accepted = 0
360
- var skipped = 0
361
- var tasks = repos.map(function (spec) {
362
- // 原始仓库行交给服务端统一解析(owner/repo、gitee:owner/repo、完整克隆地址均可)。
363
- // 客户端不再自己 indexOf 切分——全路径 URL 会被切成 owner='https:' 导致克隆失败。
364
- return fetch('/gitee-ai/scan', {
365
- method: 'POST',
366
- headers: { 'Content-Type': 'application/json' },
367
- body: JSON.stringify({ spec: spec, dryRun: false }),
368
- }).then(function (r) { return r.json().catch(function () { return { accepted: false } }) })
369
- .then(function (b) {
370
- if (b && b.accepted) accepted++
371
- else skipped++
372
- return b
373
- })
374
- })
375
- Promise.all(tasks)
376
- .then(function () {
377
- scanNoteState[1]('已排队 ' + accepted + ' 个' + (skipped ? '(跳过 ' + skipped + ' 个:正在扫/已在队列)' : '') + ',同时最多扫 ' + (draft.scanConcurrency || 3) + ' 个')
378
- })
379
- .catch(function () { scanNoteState[1](t.scanFailed) })
380
- .finally(function () { scanBusyState[1](false) })
381
- }
382
-
383
- // 一键获取我的仓库:拉取有权限的仓库(含所属组织),追加进 scanRepos 供人工勾选
384
- var normLine = (s) => String(s || '').replace(/^(gitee|github):/i, '').toLowerCase()
385
- var fetchMyRepos = () => {
386
- if (fetchBusy) return
387
- fetchBusyState[1](true)
388
- fetchNoteState[1]('')
389
- fetch('/gitee-ai/my-repos')
390
- .then((r) => r.json().catch(() => ({ ok: false, error: '响应异常' })))
391
- .then((b) => {
392
- if (!b.ok || !Array.isArray(b.repos)) { fetchNoteState[1]('获取失败:' + ((b && b.error) || '未知错误')); return }
393
- var cur = (draftRef.current.scanRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean)
394
- var seen = {}
395
- cur.forEach((s) => { seen[normLine(s)] = true })
396
- var lines = cur.slice()
397
- b.repos.forEach((x) => {
398
- var line = x.platform + ':' + x.owner + '/' + x.repo
399
- if (!seen[normLine(line)]) { lines.push(line); seen[normLine(line)] = true }
400
- })
401
- setDraftFromControl({ scanRepos: lines.join('\n') })
402
- fetchNoteState[1]('已填入 ' + b.repos.length + ' 个仓库(含既有保留,去掉不需要的再保存)')
403
- })
404
- .catch((e) => fetchNoteState[1]('获取失败:' + String(e.message || e)))
405
- .finally(() => fetchBusyState[1](false))
406
- }
407
-
408
- // ── 头部(大标题 + 描述 + 展开箭头)──
409
- var statusLine = summary
410
- ? '轮询' + (summary.pollEnabled ? ' 开 · ' + Math.round((summary.pollIntervalMs || 0) / 1000) + 's' : ' 关')
411
- + ' · 仓库 ' + ((summary.watchRepos || []).length) + ' 个'
412
- + ' · 扫描' + (summary.scanEnabled ? ' 开' : ' 关')
413
- + (summary.tokenConfigured ? '' : ' · 未配置令牌')
414
- : t.description
415
-
416
- var chevronEl = Chevron
417
- ? h(Chevron, {
418
- className: 'YyYd_a_chevron' + (open ? ' YyYd_a_chevronOpen' : ''),
419
- })
420
- : h('span', {
421
- className: 'YyYd_a_chevron' + (open ? ' YyYd_a_chevronOpen' : ''),
422
- style: { display: 'inline-flex' },
423
- }, '▾')
424
-
425
- var header = h(
426
- 'button',
427
- {
428
- type: 'button',
429
- className: 'YyYd_a_header',
430
- 'aria-expanded': open,
431
- 'aria-label': (open ? t.collapse : t.expand) + ': ' + t.title,
432
- onClick: () => openState[1](!open),
433
- },
434
- h('span', { className: 'YyYd_a_headText' },
435
- h('span', { className: 'YyYd_a_name' }, t.title),
436
- h('span', { className: 'YyYd_a_description' }, statusLine),
437
- ),
438
- dirty ? h('span', { className: 'YyYd_a_pending' }, t.unsaved) : null,
439
- chevronEl,
440
- )
441
-
442
- // ── 主体(字段 + 底部保存)──
443
- var body = null
444
- if (open) {
445
- if (!summary || !draft) {
446
- body = h(
447
- 'div',
448
- { className: 'YyYd_a_body', style: { padding: '12px 16px', color: 'var(--dsw-alias-label-tertiary)', fontSize: '13px' } },
449
- note || t.loading,
450
- )
451
- } else {
452
- var controls = []
453
- // ── 公共区:令牌 / 工作目录(两个功能模块共用,放最顶上)──
454
- controls.push(sectionBlock(t.common,
455
- 'Gitee / GitHub 令牌与仓库工作目录同时供「处理 Issue」和「代码安全扫描」使用:扫描与开发都以令牌身份访问仓库,都在同一工作目录下克隆。',
456
- '两个功能模块共用 · 修改需谨慎', '#64748b', '#64748b', 'commonBlock'))
457
- var commonFields = []
458
- commonFields.push(h('div', { key: 'tokenRow', style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(190px, 1fr))', gap: '12px', padding: '12px 0 2px' } },
459
- fieldRow(t.token, draft.tokenConfigured ? t.tokenHint : undefined, h(Input, {
460
- key: 'token',
461
- type: 'password',
462
- placeholder: draft.tokenConfigured ? '••••••••(留空保持原值)' : '输入 Gitee 私人令牌',
463
- value: (draft.token !== undefined ? draft.token : ''),
464
- onChange: (e) => setDraftFromControl({ token: e.target.value }),
465
- }), 'token'),
466
- fieldRow('GitHub 令牌(可选)', '留空保持原值;用于 github: 前缀仓库', h(Input, {
467
- key: 'githubToken',
468
- type: 'password',
469
- placeholder: draft.githubTokenConfigured ? '••••••••(留空保持原值)' : '输入 GitHub 私人令牌(可选)',
470
- value: (draft.githubToken !== undefined ? draft.githubToken : ''),
471
- onChange: (e) => setDraftFromControl({ githubToken: e.target.value }),
472
- }), 'githubToken'),
473
- ))
474
- commonFields.push(fieldRow(t.workRoot, undefined, h(Input, {
475
- key: 'workRoot',
476
- value: draft.workRoot || '',
477
- onChange: (e) => setDraftFromControl({ workRoot: e.target.value }),
478
- }), 'workRoot'))
479
- controls.push(innerCard('commonCard', '#64748b', commonFields))
480
-
481
- // ── 分区A:处理 Issue(真实操作仓库)──
482
- controls.push(sectionBlock(t.issueBlock,
483
- '监听 issue:@ 机器人后自动克隆仓库开发、推分支、提 PR,可自动合并 / 自动关闭。下面这些设置会真实操作你的仓库(改代码、推送分支、合并 PR),请仔细确认。',
484
- '会真实操作仓库 · 自动提 PR', '#dc2626', '#3b82f6', 'issueBlock'))
485
- var issueFields = []
486
- issueFields.push(fieldRow(t.bot, t.botHint, h(Input, {
487
- key: 'bot',
488
- value: draft.botName || '',
489
- onChange: (e) => setDraftFromControl({ botName: e.target.value }),
490
- }), 'bot'))
491
- issueFields.push(fieldRow(t.repos, t.reposHint, h('textarea', {
492
- key: 'repos',
493
- rows: 3,
494
- className: 'At1oFq_input',
495
- style: { height: 'auto', padding: '8px 12px', resize: 'vertical', lineHeight: 1.5 },
496
- value: draft.watchRepos || '',
497
- onChange: (e) => setDraftFromControl({ watchRepos: e.target.value }),
498
- }), 'repos'))
499
- issueFields.push(toggleRow(t.poll, undefined, draft.pollEnabled, (v) => setDraftFromControl({ pollEnabled: v }), 'poll'))
500
- issueFields.push(fieldRow(t.interval, undefined, h(Input, {
501
- key: 'interval',
502
- type: 'number',
503
- value: draft.pollIntervalMs,
504
- onChange: (e) => setDraftFromControl({ pollIntervalMs: e.target.value }),
505
- }), 'interval'))
506
- issueFields.push(toggleRow(t.merge, undefined, draft.autoMerge, (v) => setDraftFromControl({ autoMerge: v }), 'merge'))
507
- issueFields.push(toggleRow(t.close, undefined, draft.autoCloseIssue, (v) => setDraftFromControl({ autoCloseIssue: v }), 'close'))
508
- controls.push(innerCard('issueCard', '#3b82f6', issueFields))
509
-
510
- // ── 分区B:代码安全扫描(只读审计)──
511
- controls.push(sectionBlock(t.scan,
512
- '对「扫描仓库」里的仓库做静态安全审计(克隆到本地、按内置/自定义提示词检查),发现新漏洞去重后提交 [安全扫描] issue。全程只读审计,不会改代码、不会提 PR。',
513
- '只读审计 · 只提交漏洞 issue', '#059669', '#10b981', 'scanBlock'))
514
- var scanFields = []
515
- scanFields.push(toggleRow(t.scanEnable, undefined, draft.scanEnabled, (v) => setDraftFromControl({ scanEnabled: v }), 'scanEnable'))
516
- scanFields.push(fieldRow(t.scanReposLabel, t.scanReposHint, h('textarea', {
517
- key: 'scanRepos', rows: 3,
518
- className: 'At1oFq_input',
519
- style: { height: 'auto', padding: '8px 12px', resize: 'vertical', lineHeight: 1.5 },
520
- value: draft.scanRepos || '',
521
- onChange: (e) => setDraftFromControl({ scanRepos: e.target.value }),
522
- }), 'scanRepos'))
523
- scanFields.push(h('div', { key: 'fetchReposRow', style: { display: 'flex', gap: '8px', alignItems: 'center', padding: '4px 0', flexWrap: 'wrap' } },
524
- h('button', {
525
- type: 'button',
526
- onClick: fetchMyRepos,
527
- disabled: fetchBusy,
528
- style: { background: '#2f6fed', color: '#fff', border: 0, borderRadius: '8px', padding: '6px 12px', fontSize: '12px', cursor: 'pointer' },
529
- }, fetchBusy ? t.scanFetching : t.scanFetch),
530
- fetchNote ? h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } }, fetchNote) : null,
531
- ))
532
- scanFields.push(fieldRow(t.scanSeverity, t.scanSeverityHint, h('select', {
533
- key: 'scanSeverity',
534
- className: 'At1oFq_input',
535
- style: { padding: '8px 12px' },
536
- value: draft.scanMinSeverity || 'medium',
537
- onChange: (e) => setDraftFromControl({ scanMinSeverity: e.target.value }),
538
- }, ['critical', 'high', 'medium', 'low', 'none'].map((s) => h('option', { key: s, value: s }, s))), 'scanSeverity'))
539
- scanFields.push(toggleRow(t.scanOneIssue, undefined, draft.scanOneIssuePerRun, (v) => setDraftFromControl({ scanOneIssuePerRun: v }), 'scanOneIssue'))
540
- scanFields.push(fieldRow(t.scanConcurrency, undefined, h(Input, {
541
- key: 'scanConcurrency', type: 'number', min: 1, max: 8,
542
- value: draft.scanConcurrency,
543
- onChange: (e) => setDraftFromControl({ scanConcurrency: e.target.value }),
544
- }), 'scanConcurrency'))
545
- scanFields.push(fieldRow(t.scanPromptsLabel, t.scanPromptsHint, h('textarea', {
546
- key: 'scanPrompts', rows: 4,
547
- className: 'At1oFq_input',
548
- style: { height: 'auto', padding: '8px 12px', resize: 'vertical', lineHeight: 1.4, fontFamily: 'Consolas,monospace', fontSize: '11px' },
549
- value: draft.scanPrompts || '',
550
- onChange: (e) => setDraftFromControl({ scanPrompts: e.target.value }),
551
- }), 'scanPrompts'))
552
- scanFields.push(fieldRow(t.scanInterval, undefined, h(Input, {
553
- key: 'scanInterval', type: 'number',
554
- value: draft.scanIntervalMs,
555
- onChange: (e) => setDraftFromControl({ scanIntervalMs: e.target.value }),
556
- }), 'scanInterval'))
557
- scanFields.push(h('div', { key: 'scanNowRow', style: { display: 'flex', gap: '8px', alignItems: 'center', padding: '8px 0', flexWrap: 'wrap' } },
558
- h('button', {
559
- type: 'button',
560
- onClick: scanNow,
561
- disabled: scanBusy,
562
- style: { background: 'var(--dsw-alias-danger, #dc2626)', color: '#fff', border: 0, borderRadius: '8px', padding: '8px 14px', fontSize: '13px', cursor: 'pointer' },
563
- }, scanBusy ? t.scanning : t.scanNow),
564
- scanNote ? h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } }, scanNote) : null,
565
- ))
566
- controls.push(innerCard('scanCard', '#10b981', scanFields))
567
-
568
- body = h(
569
- 'div',
570
- { className: 'YyYd_a_body' },
571
- h('a', {
572
- href: '/gitee-ai/settings',
573
- target: '_blank',
574
- rel: 'noreferrer',
575
- style: { display: 'inline-block', marginTop: '12px', fontSize: '12px', color: 'var(--dsw-alias-label-secondary)', textDecoration: 'underline' },
576
- }, t.status + ' ↗'),
577
- controls,
578
- note
579
- ? h('p', {
580
- className: failed ? 'YyYd_a_failed' : 'YyYd_a_readOnly',
581
- role: 'status',
582
- style: failed ? {} : { margin: '8px 0 0', fontSize: '12px', lineHeight: '1.5' },
583
- }, note)
584
- : null,
585
- h('div', { className: 'YyYd_a_footer' },
586
- h('button', {
587
- type: 'button',
588
- className: 'YyYd_a_discard',
589
- disabled: !dirty || busy,
590
- onClick: discard,
591
- }, t.discard),
592
- h('button', {
593
- type: 'button',
594
- className: 'YyYd_a_save',
595
- disabled: busy,
596
- onClick: save,
597
- }, busy ? t.saving : t.save),
598
- ),
599
- )
600
- }
601
- }
602
-
603
- return h(
604
- 'li',
605
- { className: 'YyYd_a_card' + (open ? ' YyYd_a_cardOpen' : '') },
606
- header,
607
- body,
608
- )
609
- }
610
- }
611
-
612
- function registerCard(ctx) {
613
- if (typeof ctx.inject !== 'function') return
614
- ctx.inject(['slots'], (scope) => {
615
- fetch('/gitee-ai/config')
616
- .then((response) => {
617
- if (response.status === 404) return
618
- try {
619
- mountCard(scope)
620
- } catch (error) {
621
- console.error('[gitee-ai] settings card skipped: ' + error)
622
- }
623
- })
624
- .catch(() => {})
625
- })
626
- }
627
-
628
- function mountCard(ctx) {
629
- var react
630
- try {
631
- react = require('react')
632
- } catch (error) {
633
- console.error('[gitee-ai] settings card skipped: ' + error)
634
- return
635
- }
636
- var ui
637
- try {
638
- ui = require('@deepseek-ai/dsh-client-ui-primitives')
639
- } catch (error) {
640
- ui = {}
641
- }
642
- var Card = ConfigCard(react, ui)
643
- ctx.slots.inject('settings.plugin.item', function* () {
644
- yield ctx.slots.register({ name: 'settings.plugin.item', id: 'gitee-ai-employee', key: 'gitee-ai-employee', order: 30 }, Card)
645
- })
646
- }
647
-
648
- function apply(ctx) {
649
- registerCard(ctx)
650
- if (typeof ctx.effect === 'function') {
651
- ctx.effect(() => () => {}, 'gitee-ai: settings card')
652
- }
653
- }
654
-
655
- exports.apply = apply
656
- // `slots` is optional, so it is not required here: registerCard checks.
657
- exports.inject = []
658
- return module.exports
659
- },
660
- })
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
+ common: '连接与公共配置',
47
+ issueBlock: '处理 Issue(自动开发 · 提 PR)',
48
+ scan: '代码安全扫描',
49
+ scanEnable: '启用定时扫描(全部仓库排队持续扫描,去重幂等)',
50
+ scanReposLabel: '扫描仓库',
51
+ scanReposHint: '每行一个 owner/repo 或完整克隆地址(https://gitee.com/owner/repo.git、git@github.com:owner/repo.git 均可),[gitee:|github:] 前缀可选',
52
+ scanSeverity: '最低上报级别',
53
+ scanSeverityHint: 'critical / high / medium / low / none',
54
+ scanOneIssue: '一次扫描合并为一个 issue',
55
+ scanConcurrency: '同时扫描仓库数(1~8,默认 3,自动排队)',
56
+ scanPromptsLabel: '自定义提示词(JSON)',
57
+ scanPromptsHint: '{"id":{"name":"名称","prompt":"内容"}},同 id 覆盖内置,留空用内置',
58
+ scanInterval: '扫描间隔(毫秒)',
59
+ scanFetch: '一键获取我的仓库',
60
+ scanFetching: '获取中…',
61
+ scanNow: '立即扫描全部仓库',
62
+ scanning: '触发中…',
63
+ scanDone: '已触发,结果见 /gitee-ai/scan',
64
+ scanFailed: '触发失败',
65
+ scanNeedRepos: '请先填写扫描仓库',
66
+ }
67
+ }
68
+
69
+ function ConfigCard(react, ui) {
70
+ var h = react.createElement
71
+ var Input = ui && ui.Input
72
+ var Chevron = ui && ui.IconChevronDownOutline14
73
+
74
+ // 复用内置 settings-plugins 注入的字段样式类(At1oFq_*)
75
+ var fieldRow = (label, hint, control, key, groupName) =>
76
+ h(
77
+ groupName ? 'div' : 'label',
78
+ {
79
+ key: key,
80
+ role: groupName ? 'group' : undefined,
81
+ 'aria-label': groupName || undefined,
82
+ className: 'At1oFq_field',
83
+ style: { display: 'flex', flexDirection: 'column', gap: '6px', padding: '12px 0' },
84
+ },
85
+ h('div', { className: 'At1oFq_head' },
86
+ h('span', { className: 'At1oFq_label' }, label),
87
+ ),
88
+ hint
89
+ ? h('div', { style: { fontSize: '12px', lineHeight: '17px', color: 'var(--dsw-alias-label-tertiary)', display: 'block', whiteSpace: 'normal', overflow: 'visible', textOverflow: 'clip' } }, hint)
90
+ : null,
91
+ control,
92
+ )
93
+
94
+ // 分区标题块:色条 + 标题 + 行为标签 + 说明,
95
+ // 用于把「处理 Issue」与「代码安全扫描」两个功能模块清楚分隔,防止误配置/误操作。
96
+ var sectionBlock = (label, sub, tag, tagBg, accent, key) =>
97
+ h('div', {
98
+ key: key,
99
+ style: {
100
+ margin: '16px 0 6px',
101
+ padding: '10px 12px',
102
+ borderRadius: '10px',
103
+ border: '1px solid ' + accent,
104
+ background: 'linear-gradient(90deg, ' + accent + '1A, transparent 75%)',
105
+ display: 'flex', flexDirection: 'column', gap: '4px',
106
+ },
107
+ },
108
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' } },
109
+ h('span', { style: { width: '6px', height: '16px', borderRadius: '3px', background: accent, display: 'inline-block' } }),
110
+ h('span', { style: { fontSize: '14px', fontWeight: 800, color: 'var(--dsw-alias-label-primary)' } }, label),
111
+ tag ? h('span', { style: { fontSize: '11px', fontWeight: 700, color: '#fff', background: tagBg, borderRadius: '999px', padding: '2px 8px' } }, tag) : null,
112
+ ),
113
+ sub ? h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)', lineHeight: 1.45 } }, sub) : null,
114
+ )
115
+
116
+ // 分区内层卡片:把某一模块的全部字段包进带浅色描边的容器,与另一模块形成视觉隔离。
117
+ var innerCard = (key, accent, children) =>
118
+ h('div', {
119
+ key: key,
120
+ style: {
121
+ border: '1px solid ' + accent + '59',
122
+ borderRadius: '10px',
123
+ padding: '2px 14px 6px',
124
+ margin: '2px 0 2px',
125
+ background: accent + '0D',
126
+ },
127
+ }, children)
128
+
129
+ // 自定义开关行:左侧标签,右侧紧凑的 iOS 风格 toggle
130
+ var toggleRow = (label, hint, checked, onChange, key) =>
131
+ h(
132
+ 'div',
133
+ {
134
+ key: key,
135
+ className: 'At1oFq_field',
136
+ style: { display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: '12px', padding: '10px 0' },
137
+ },
138
+ h('div', { style: { minWidth: 0, display: 'flex', flexDirection: 'column', gap: '2px' } },
139
+ h('span', {
140
+ className: 'At1oFq_label',
141
+ style: { margin: 0, fontSize: '13px', fontWeight: 500, color: 'var(--dsw-alias-label-primary)' },
142
+ }, label),
143
+ hint
144
+ ? h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)', lineHeight: 1.4 } }, hint)
145
+ : null,
146
+ ),
147
+ h(Switch, { checked: !!checked, onChange, label }),
148
+ )
149
+
150
+ // 紧凑圆形开关(轨道 + 滑块),用 DSH 主题 token 着色
151
+ function Switch(props) {
152
+ var on = !!props.checked
153
+ return h(
154
+ 'button',
155
+ {
156
+ type: 'button',
157
+ role: 'switch',
158
+ 'aria-checked': on,
159
+ 'aria-label': props.label,
160
+ onClick: () => props.onChange(!on),
161
+ style: {
162
+ appearance: 'none',
163
+ flex: 'none',
164
+ width: '36px',
165
+ height: '20px',
166
+ borderRadius: '999px',
167
+ border: 0,
168
+ padding: 0,
169
+ cursor: 'pointer',
170
+ position: 'relative',
171
+ background: on
172
+ ? 'var(--dsw-alias-brand-primary, #2563eb)'
173
+ : 'var(--dsw-alias-bg-module-platform, rgba(127,127,127,0.35))',
174
+ transition: 'background .16s',
175
+ outline: 'none',
176
+ boxShadow: 'inset 0 0 0 1px rgba(127,127,127,0.25)',
177
+ },
178
+ },
179
+ h('span', {
180
+ style: {
181
+ position: 'absolute',
182
+ top: '2px',
183
+ left: on ? '18px' : '2px',
184
+ width: '16px',
185
+ height: '16px',
186
+ borderRadius: '999px',
187
+ background: '#fff',
188
+ boxShadow: '0 1px 2px rgba(0,0,0,0.35)',
189
+ transition: 'left .16s',
190
+ },
191
+ }),
192
+ )
193
+ }
194
+
195
+ // 平衡:onChange 里用 ref 读最新 draft,避免闭包陈旧
196
+ var draftRef = { current: null }
197
+
198
+ return function GiteeCard() {
199
+ var t = labels()
200
+ var openState = react.useState(false)
201
+ var summaryState = react.useState(null)
202
+ var draftState = react.useState(null)
203
+ var busyState = react.useState(false)
204
+ var noteState = react.useState('')
205
+ var failedState = react.useState(false)
206
+ var dirtyState = react.useState(false)
207
+ var open = openState[0]
208
+ var summary = summaryState[0]
209
+ var draft = draftState[0]
210
+ var busy = busyState[0]
211
+ var note = noteState[0]
212
+ var failed = failedState[0]
213
+ var dirty = dirtyState[0]
214
+ var scanBusyState = react.useState(false)
215
+ var scanNoteState = react.useState('')
216
+ var scanBusy = scanBusyState[0]
217
+ var scanNote = scanNoteState[0]
218
+ var fetchBusyState = react.useState(false)
219
+ var fetchNoteState = react.useState('')
220
+ var fetchBusy = fetchBusyState[0]
221
+ var fetchNote = fetchNoteState[0]
222
+
223
+ draftRef.current = draft
224
+
225
+ var seedDraft = (config) => ({
226
+ botName: (config && config.botName) || '',
227
+ workRoot: (config && config.workRoot) || '',
228
+ watchRepos: ((config && config.watchRepos) || []).join('\n'),
229
+ pollEnabled: !!(config && config.pollEnabled),
230
+ pollIntervalMs: (config && config.pollIntervalMs) || 60000,
231
+ autoMerge: !!(config && config.autoMerge),
232
+ autoCloseIssue: !!(config && config.autoCloseIssue),
233
+ tokenConfigured: !!(config && config.tokenConfigured),
234
+ githubTokenConfigured: !!(config && config.githubTokenConfigured),
235
+ scanEnabled: !!(config && config.scanEnabled),
236
+ scanRepos: ((config && config.scanRepos) || []).join('\n'),
237
+ scanMinSeverity: (config && config.scanMinSeverity) || 'medium',
238
+ scanOneIssuePerRun: (config && config.scanOneIssuePerRun) !== false,
239
+ scanPrompts: (config && config.scanPrompts && Object.keys(config.scanPrompts).length)
240
+ ? JSON.stringify(config.scanPrompts, null, 2) : '',
241
+ scanIntervalMs: (config && config.scanIntervalMs) || 21600000,
242
+ scanConcurrency: (config && config.scanConcurrency) || 3,
243
+ })
244
+
245
+ var load = react.useCallback(() => {
246
+ fetch('/gitee-ai/config')
247
+ .then((r) => r.json().then((body) => {
248
+ if (!r.ok) throw new Error(body.error || 'load failed')
249
+ return body
250
+ }))
251
+ .then((body) => {
252
+ summaryState[1](body.config)
253
+ var d = seedDraft(body.config)
254
+ draftRef.current = d
255
+ draftState[1](d)
256
+ noteState[1]('')
257
+ failedState[1](false)
258
+ })
259
+ .catch((error) => {
260
+ noteState[1](String(error.message ? error.message : error))
261
+ failedState[1](true)
262
+ })
263
+ }, [])
264
+
265
+ react.useEffect(() => {
266
+ if (open && summary === null) load()
267
+ }, [open, summary, load])
268
+
269
+ var setDraft = (d) => { draftState[1](d); dirtyState[1](true) }
270
+ var setDraftFromControl = (patch) => {
271
+ var d = Object.assign({}, draftRef.current, patch)
272
+ draftRef.current = d
273
+ draftState[1](d)
274
+ dirtyState[1](true)
275
+ }
276
+
277
+ var save = () => {
278
+ if (!draft) return
279
+ var prompts = {}
280
+ if (draft.scanPrompts && draft.scanPrompts.trim()) {
281
+ try {
282
+ var parsed = JSON.parse(draft.scanPrompts)
283
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
284
+ for (var id in parsed) {
285
+ var pv = parsed[id] || {}
286
+ var nm = String(pv.name || '').trim()
287
+ var pt = String(pv.prompt || '').trim()
288
+ if (nm && pt) prompts[id] = { name: nm, prompt: pt }
289
+ }
290
+ }
291
+ } catch (e) {
292
+ noteState[1]('提示词 JSON 格式错误,请检查')
293
+ failedState[1](true)
294
+ return
295
+ }
296
+ }
297
+ busyState[1](true)
298
+ failedState[1](false)
299
+ noteState[1]('')
300
+ var payload = {
301
+ ...((draft.token && draft.token.trim()) ? { giteeToken: draft.token.trim() } : {}),
302
+ ...((draft.githubToken && draft.githubToken.trim()) ? { githubToken: draft.githubToken.trim() } : {}),
303
+ botName: draft.botName,
304
+ workRoot: draft.workRoot,
305
+ watchRepos: (draft.watchRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean),
306
+ pollEnabled: draft.pollEnabled,
307
+ pollIntervalMs: Number(draft.pollIntervalMs) || 60000,
308
+ autoMerge: draft.autoMerge,
309
+ autoCloseIssue: draft.autoCloseIssue,
310
+ scanEnabled: !!draft.scanEnabled,
311
+ scanRepos: (draft.scanRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean),
312
+ scanMinSeverity: draft.scanMinSeverity || 'medium',
313
+ scanOneIssuePerRun: draft.scanOneIssuePerRun !== false,
314
+ scanPrompts: prompts,
315
+ scanIntervalMs: Number(draft.scanIntervalMs) || 21600000,
316
+ scanConcurrency: Number(draft.scanConcurrency) || 3,
317
+ }
318
+ fetch('/gitee-ai/config', {
319
+ method: 'POST',
320
+ headers: { 'Content-Type': 'application/json' },
321
+ body: JSON.stringify(payload),
322
+ })
323
+ .then((r) => r.json().then((body) => {
324
+ if (!r.ok) throw new Error(body.error || 'save failed')
325
+ return body
326
+ }))
327
+ .then((body) => {
328
+ noteState[1](t.savedDone)
329
+ summaryState[1](body.config)
330
+ var d = seedDraft(body.config)
331
+ draftRef.current = d
332
+ draftState[1](d)
333
+ dirtyState[1](false)
334
+ })
335
+ .catch((error) => {
336
+ noteState[1](String(error.message ? error.message : error))
337
+ failedState[1](true)
338
+ })
339
+ .finally(() => busyState[1](false))
340
+ }
341
+
342
+ var discard = () => {
343
+ if (!summary) return
344
+ var d = seedDraft(summary)
345
+ draftRef.current = d
346
+ draftState[1](d)
347
+ dirtyState[1](false)
348
+ noteState[1]('')
349
+ failedState[1](false)
350
+ }
351
+
352
+ // 立即扫描:对 draft 里的 scanRepos 逐个 POST /gitee-ai/scan(服务端自动排队,按并发上限持续扫描)
353
+ var scanNow = () => {
354
+ if (!draft) return
355
+ var repos = (draft.scanRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean)
356
+ if (!repos.length) { scanNoteState[1](t.scanNeedRepos); return }
357
+ scanBusyState[1](true)
358
+ scanNoteState[1]('')
359
+ var accepted = 0
360
+ var skipped = 0
361
+ var tasks = repos.map(function (spec) {
362
+ // 原始仓库行交给服务端统一解析(owner/repo、gitee:owner/repo、完整克隆地址均可)。
363
+ // 客户端不再自己 indexOf 切分——全路径 URL 会被切成 owner='https:' 导致克隆失败。
364
+ return fetch('/gitee-ai/scan', {
365
+ method: 'POST',
366
+ headers: { 'Content-Type': 'application/json' },
367
+ body: JSON.stringify({ spec: spec, dryRun: false }),
368
+ }).then(function (r) { return r.json().catch(function () { return { accepted: false } }) })
369
+ .then(function (b) {
370
+ if (b && b.accepted) accepted++
371
+ else skipped++
372
+ return b
373
+ })
374
+ })
375
+ Promise.all(tasks)
376
+ .then(function () {
377
+ scanNoteState[1]('已排队 ' + accepted + ' 个' + (skipped ? '(跳过 ' + skipped + ' 个:正在扫/已在队列)' : '') + ',同时最多扫 ' + (draft.scanConcurrency || 3) + ' 个')
378
+ })
379
+ .catch(function () { scanNoteState[1](t.scanFailed) })
380
+ .finally(function () { scanBusyState[1](false) })
381
+ }
382
+
383
+ // 一键获取我的仓库:拉取有权限的仓库(含所属组织),追加进 scanRepos 供人工勾选
384
+ var normLine = (s) => String(s || '').replace(/^(gitee|github):/i, '').toLowerCase()
385
+ var fetchMyRepos = () => {
386
+ if (fetchBusy) return
387
+ fetchBusyState[1](true)
388
+ fetchNoteState[1]('')
389
+ fetch('/gitee-ai/my-repos')
390
+ .then((r) => r.json().catch(() => ({ ok: false, error: '响应异常' })))
391
+ .then((b) => {
392
+ if (!b.ok || !Array.isArray(b.repos)) { fetchNoteState[1]('获取失败:' + ((b && b.error) || '未知错误')); return }
393
+ var cur = (draftRef.current.scanRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean)
394
+ var seen = {}
395
+ cur.forEach((s) => { seen[normLine(s)] = true })
396
+ var lines = cur.slice()
397
+ b.repos.forEach((x) => {
398
+ var line = x.platform + ':' + x.owner + '/' + x.repo
399
+ if (!seen[normLine(line)]) { lines.push(line); seen[normLine(line)] = true }
400
+ })
401
+ setDraftFromControl({ scanRepos: lines.join('\n') })
402
+ fetchNoteState[1]('已填入 ' + b.repos.length + ' 个仓库(含既有保留,去掉不需要的再保存)')
403
+ })
404
+ .catch((e) => fetchNoteState[1]('获取失败:' + String(e.message || e)))
405
+ .finally(() => fetchBusyState[1](false))
406
+ }
407
+
408
+ // ── 头部(大标题 + 描述 + 展开箭头)──
409
+ var statusLine = summary
410
+ ? '轮询' + (summary.pollEnabled ? ' 开 · ' + Math.round((summary.pollIntervalMs || 0) / 1000) + 's' : ' 关')
411
+ + ' · 仓库 ' + ((summary.watchRepos || []).length) + ' 个'
412
+ + ' · 扫描' + (summary.scanEnabled ? ' 开' : ' 关')
413
+ + (summary.tokenConfigured ? '' : ' · 未配置令牌')
414
+ : t.description
415
+
416
+ var chevronEl = Chevron
417
+ ? h(Chevron, {
418
+ className: 'YyYd_a_chevron' + (open ? ' YyYd_a_chevronOpen' : ''),
419
+ })
420
+ : h('span', {
421
+ className: 'YyYd_a_chevron' + (open ? ' YyYd_a_chevronOpen' : ''),
422
+ style: { display: 'inline-flex' },
423
+ }, '▾')
424
+
425
+ var header = h(
426
+ 'button',
427
+ {
428
+ type: 'button',
429
+ className: 'YyYd_a_header',
430
+ 'aria-expanded': open,
431
+ 'aria-label': (open ? t.collapse : t.expand) + ': ' + t.title,
432
+ onClick: () => openState[1](!open),
433
+ },
434
+ h('span', { className: 'YyYd_a_headText' },
435
+ h('span', { className: 'YyYd_a_name' }, t.title),
436
+ h('span', { className: 'YyYd_a_description' }, statusLine),
437
+ ),
438
+ dirty ? h('span', { className: 'YyYd_a_pending' }, t.unsaved) : null,
439
+ chevronEl,
440
+ )
441
+
442
+ // ── 主体(字段 + 底部保存)──
443
+ var body = null
444
+ if (open) {
445
+ if (!summary || !draft) {
446
+ body = h(
447
+ 'div',
448
+ { className: 'YyYd_a_body', style: { padding: '12px 16px', color: 'var(--dsw-alias-label-tertiary)', fontSize: '13px' } },
449
+ note || t.loading,
450
+ )
451
+ } else {
452
+ var controls = []
453
+ // ── 公共区:令牌 / 工作目录(两个功能模块共用,放最顶上)──
454
+ controls.push(sectionBlock(t.common,
455
+ 'Gitee / GitHub 令牌与仓库工作目录同时供「处理 Issue」和「代码安全扫描」使用:扫描与开发都以令牌身份访问仓库,都在同一工作目录下克隆。',
456
+ '两个功能模块共用 · 修改需谨慎', '#64748b', '#64748b', 'commonBlock'))
457
+ var commonFields = []
458
+ commonFields.push(h('div', { key: 'tokenRow', style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(190px, 1fr))', gap: '12px', padding: '12px 0 2px' } },
459
+ fieldRow(t.token, draft.tokenConfigured ? t.tokenHint : undefined, h(Input, {
460
+ key: 'token',
461
+ type: 'password',
462
+ placeholder: draft.tokenConfigured ? '••••••••(留空保持原值)' : '输入 Gitee 私人令牌',
463
+ value: (draft.token !== undefined ? draft.token : ''),
464
+ onChange: (e) => setDraftFromControl({ token: e.target.value }),
465
+ }), 'token'),
466
+ fieldRow('GitHub 令牌(可选)', '留空保持原值;用于 github: 前缀仓库', h(Input, {
467
+ key: 'githubToken',
468
+ type: 'password',
469
+ placeholder: draft.githubTokenConfigured ? '••••••••(留空保持原值)' : '输入 GitHub 私人令牌(可选)',
470
+ value: (draft.githubToken !== undefined ? draft.githubToken : ''),
471
+ onChange: (e) => setDraftFromControl({ githubToken: e.target.value }),
472
+ }), 'githubToken'),
473
+ ))
474
+ commonFields.push(fieldRow(t.workRoot, undefined, h(Input, {
475
+ key: 'workRoot',
476
+ value: draft.workRoot || '',
477
+ onChange: (e) => setDraftFromControl({ workRoot: e.target.value }),
478
+ }), 'workRoot'))
479
+ controls.push(innerCard('commonCard', '#64748b', commonFields))
480
+
481
+ // ── 分区A:处理 Issue(真实操作仓库)──
482
+ controls.push(sectionBlock(t.issueBlock,
483
+ '监听 issue:@ 机器人后自动克隆仓库开发、推分支、提 PR,可自动合并 / 自动关闭。下面这些设置会真实操作你的仓库(改代码、推送分支、合并 PR),请仔细确认。',
484
+ '会真实操作仓库 · 自动提 PR', '#dc2626', '#3b82f6', 'issueBlock'))
485
+ var issueFields = []
486
+ issueFields.push(fieldRow(t.bot, t.botHint, h(Input, {
487
+ key: 'bot',
488
+ value: draft.botName || '',
489
+ onChange: (e) => setDraftFromControl({ botName: e.target.value }),
490
+ }), 'bot'))
491
+ issueFields.push(fieldRow(t.repos, t.reposHint, h('textarea', {
492
+ key: 'repos',
493
+ rows: 3,
494
+ className: 'At1oFq_input',
495
+ style: { height: 'auto', padding: '8px 12px', resize: 'vertical', lineHeight: 1.5 },
496
+ value: draft.watchRepos || '',
497
+ onChange: (e) => setDraftFromControl({ watchRepos: e.target.value }),
498
+ }), 'repos'))
499
+ issueFields.push(toggleRow(t.poll, undefined, draft.pollEnabled, (v) => setDraftFromControl({ pollEnabled: v }), 'poll'))
500
+ issueFields.push(fieldRow(t.interval, undefined, h(Input, {
501
+ key: 'interval',
502
+ type: 'number',
503
+ value: draft.pollIntervalMs,
504
+ onChange: (e) => setDraftFromControl({ pollIntervalMs: e.target.value }),
505
+ }), 'interval'))
506
+ issueFields.push(toggleRow(t.merge, undefined, draft.autoMerge, (v) => setDraftFromControl({ autoMerge: v }), 'merge'))
507
+ issueFields.push(toggleRow(t.close, undefined, draft.autoCloseIssue, (v) => setDraftFromControl({ autoCloseIssue: v }), 'close'))
508
+ controls.push(innerCard('issueCard', '#3b82f6', issueFields))
509
+
510
+ // ── 分区B:代码安全扫描(只读审计)──
511
+ controls.push(sectionBlock(t.scan,
512
+ '对「扫描仓库」里的仓库做静态安全审计(克隆到本地、按内置/自定义提示词检查),发现新漏洞去重后提交 [安全扫描] issue。全程只读审计,不会改代码、不会提 PR。',
513
+ '只读审计 · 只提交漏洞 issue', '#059669', '#10b981', 'scanBlock'))
514
+ var scanFields = []
515
+ scanFields.push(toggleRow(t.scanEnable, undefined, draft.scanEnabled, (v) => setDraftFromControl({ scanEnabled: v }), 'scanEnable'))
516
+ scanFields.push(fieldRow(t.scanReposLabel, t.scanReposHint, h('textarea', {
517
+ key: 'scanRepos', rows: 3,
518
+ className: 'At1oFq_input',
519
+ style: { height: 'auto', padding: '8px 12px', resize: 'vertical', lineHeight: 1.5 },
520
+ value: draft.scanRepos || '',
521
+ onChange: (e) => setDraftFromControl({ scanRepos: e.target.value }),
522
+ }), 'scanRepos'))
523
+ scanFields.push(h('div', { key: 'fetchReposRow', style: { display: 'flex', gap: '8px', alignItems: 'center', padding: '4px 0', flexWrap: 'wrap' } },
524
+ h('button', {
525
+ type: 'button',
526
+ onClick: fetchMyRepos,
527
+ disabled: fetchBusy,
528
+ style: { background: '#2f6fed', color: '#fff', border: 0, borderRadius: '8px', padding: '6px 12px', fontSize: '12px', cursor: 'pointer' },
529
+ }, fetchBusy ? t.scanFetching : t.scanFetch),
530
+ fetchNote ? h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } }, fetchNote) : null,
531
+ ))
532
+ scanFields.push(fieldRow(t.scanSeverity, t.scanSeverityHint, h('select', {
533
+ key: 'scanSeverity',
534
+ className: 'At1oFq_input',
535
+ style: { padding: '8px 12px' },
536
+ value: draft.scanMinSeverity || 'medium',
537
+ onChange: (e) => setDraftFromControl({ scanMinSeverity: e.target.value }),
538
+ }, ['critical', 'high', 'medium', 'low', 'none'].map((s) => h('option', { key: s, value: s }, s))), 'scanSeverity'))
539
+ scanFields.push(toggleRow(t.scanOneIssue, undefined, draft.scanOneIssuePerRun, (v) => setDraftFromControl({ scanOneIssuePerRun: v }), 'scanOneIssue'))
540
+ scanFields.push(fieldRow(t.scanConcurrency, undefined, h(Input, {
541
+ key: 'scanConcurrency', type: 'number', min: 1, max: 8,
542
+ value: draft.scanConcurrency,
543
+ onChange: (e) => setDraftFromControl({ scanConcurrency: e.target.value }),
544
+ }), 'scanConcurrency'))
545
+ scanFields.push(fieldRow(t.scanPromptsLabel, t.scanPromptsHint, h('textarea', {
546
+ key: 'scanPrompts', rows: 4,
547
+ className: 'At1oFq_input',
548
+ style: { height: 'auto', padding: '8px 12px', resize: 'vertical', lineHeight: 1.4, fontFamily: 'Consolas,monospace', fontSize: '11px' },
549
+ value: draft.scanPrompts || '',
550
+ onChange: (e) => setDraftFromControl({ scanPrompts: e.target.value }),
551
+ }), 'scanPrompts'))
552
+ scanFields.push(fieldRow(t.scanInterval, undefined, h(Input, {
553
+ key: 'scanInterval', type: 'number',
554
+ value: draft.scanIntervalMs,
555
+ onChange: (e) => setDraftFromControl({ scanIntervalMs: e.target.value }),
556
+ }), 'scanInterval'))
557
+ scanFields.push(h('div', { key: 'scanNowRow', style: { display: 'flex', gap: '8px', alignItems: 'center', padding: '8px 0', flexWrap: 'wrap' } },
558
+ h('button', {
559
+ type: 'button',
560
+ onClick: scanNow,
561
+ disabled: scanBusy,
562
+ style: { background: 'var(--dsw-alias-danger, #dc2626)', color: '#fff', border: 0, borderRadius: '8px', padding: '8px 14px', fontSize: '13px', cursor: 'pointer' },
563
+ }, scanBusy ? t.scanning : t.scanNow),
564
+ scanNote ? h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } }, scanNote) : null,
565
+ ))
566
+ controls.push(innerCard('scanCard', '#10b981', scanFields))
567
+
568
+ body = h(
569
+ 'div',
570
+ { className: 'YyYd_a_body' },
571
+ h('a', {
572
+ href: '/gitee-ai/settings',
573
+ target: '_blank',
574
+ rel: 'noreferrer',
575
+ style: { display: 'inline-block', marginTop: '12px', fontSize: '12px', color: 'var(--dsw-alias-label-secondary)', textDecoration: 'underline' },
576
+ }, t.status + ' ↗'),
577
+ controls,
578
+ note
579
+ ? h('p', {
580
+ className: failed ? 'YyYd_a_failed' : 'YyYd_a_readOnly',
581
+ role: 'status',
582
+ style: failed ? {} : { margin: '8px 0 0', fontSize: '12px', lineHeight: '1.5' },
583
+ }, note)
584
+ : null,
585
+ h('div', { className: 'YyYd_a_footer' },
586
+ h('button', {
587
+ type: 'button',
588
+ className: 'YyYd_a_discard',
589
+ disabled: !dirty || busy,
590
+ onClick: discard,
591
+ }, t.discard),
592
+ h('button', {
593
+ type: 'button',
594
+ className: 'YyYd_a_save',
595
+ disabled: busy,
596
+ onClick: save,
597
+ }, busy ? t.saving : t.save),
598
+ ),
599
+ )
600
+ }
601
+ }
602
+
603
+ return h(
604
+ 'li',
605
+ { className: 'YyYd_a_card' + (open ? ' YyYd_a_cardOpen' : '') },
606
+ header,
607
+ body,
608
+ )
609
+ }
610
+ }
611
+
612
+ function registerCard(ctx) {
613
+ if (typeof ctx.inject !== 'function') return
614
+ ctx.inject(['slots'], (scope) => {
615
+ fetch('/gitee-ai/config')
616
+ .then((response) => {
617
+ if (response.status === 404) return
618
+ try {
619
+ mountCard(scope)
620
+ } catch (error) {
621
+ console.error('[gitee-ai] settings card skipped: ' + error)
622
+ }
623
+ })
624
+ .catch(() => {})
625
+ })
626
+ }
627
+
628
+ function mountCard(ctx) {
629
+ var react
630
+ try {
631
+ react = require('react')
632
+ } catch (error) {
633
+ console.error('[gitee-ai] settings card skipped: ' + error)
634
+ return
635
+ }
636
+ var ui
637
+ try {
638
+ ui = require('@deepseek-ai/dsh-client-ui-primitives')
639
+ } catch (error) {
640
+ ui = {}
641
+ }
642
+ var Card = ConfigCard(react, ui)
643
+ ctx.slots.inject('settings.plugin.item', function* () {
644
+ yield ctx.slots.register({ name: 'settings.plugin.item', id: 'gitee-ai-employee', key: 'gitee-ai-employee', order: 30 }, Card)
645
+ })
646
+ }
647
+
648
+ function apply(ctx) {
649
+ registerCard(ctx)
650
+ if (typeof ctx.effect === 'function') {
651
+ ctx.effect(() => () => {}, 'gitee-ai: settings card')
652
+ }
653
+ }
654
+
655
+ exports.apply = apply
656
+ // `slots` is optional, so it is not required here: registerCard checks.
657
+ exports.inject = []
658
+ return module.exports
659
+ },
660
+ })