@dsh-plus/agent-preset-rea 0.1.1 → 0.1.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/lib/client.js +698 -114
- package/lib/index.d.ts +216 -20
- package/lib/index.js +868 -204
- package/package.json +5 -5
- package/src/client/api.ts +7 -6
- package/src/client/card.tsx +75 -7
- package/src/client/client.ts +3 -2
- package/src/client/draft.ts +34 -3
- package/src/client/i18n.ts +66 -8
- package/src/client/install.tsx +157 -58
- package/src/client/styles.ts +8 -0
- package/src/client/toggles.tsx +57 -0
- package/src/config.ts +61 -11
- package/src/definition.ts +61 -12
- package/src/dto.ts +29 -3
- package/src/exec.ts +34 -12
- package/src/index.ts +38 -1
- package/src/manager.ts +14 -9
- package/src/persona-prefix.ts +367 -0
- package/src/reverse.ts +335 -0
- package/src/routes.ts +108 -66
- package/src/skill.ts +23 -8
package/src/client/install.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 卡片的「REA 安装与更新」区:只读状态表 +
|
|
3
|
-
*
|
|
2
|
+
* 卡片的「REA 安装与更新」区:只读状态表 + 四个动作(安装/更新 REA、更新 REA skill、
|
|
3
|
+
* 更新 reverse-skill、取消作业)+ 运行中作业的阶段进度条、已用时与日志尾部。
|
|
4
4
|
*
|
|
5
5
|
* 状态与作业都来自宿主半的同源端点(见 client/api.ts 与宿主 routes.ts):这里只管
|
|
6
6
|
* 轮询与呈现。作业在跑时按 {@link PROGRESS_POLL_MS} 轮询——进度是宿主侧的真事实,
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* @module agent-preset-rea/client/install
|
|
9
9
|
*/
|
|
10
10
|
import { Button } from '@dsh-plus/shared/client'
|
|
11
|
-
import { type ReactElement, useCallback, useEffect, useRef, useState } from 'react'
|
|
11
|
+
import { type ReactElement, type ReactNode, useCallback, useEffect, useRef, useState } from 'react'
|
|
12
12
|
|
|
13
|
-
import type { JobDto, StatusDto } from '../dto.ts'
|
|
13
|
+
import type { JobDto, ReverseSkillDto, SkillDto, StatusDto } from '../dto.ts'
|
|
14
14
|
import { cancelJob, fetchStatus, startJob } from './api.ts'
|
|
15
15
|
import type { Translate } from './i18n.ts'
|
|
16
16
|
|
|
@@ -43,6 +43,14 @@ const STATE_CLASS = {
|
|
|
43
43
|
cancelled: 'Cancelled',
|
|
44
44
|
} as const
|
|
45
45
|
|
|
46
|
+
/** 作业种类 → 文案键。 */
|
|
47
|
+
const JOB_LABEL = { install: 'jobInstall', skill: 'jobSkill', reverse: 'jobReverse' } as const
|
|
48
|
+
|
|
49
|
+
/** 进度条百分比(防御性收窄,避免越界宽度)。 */
|
|
50
|
+
function widthOf(percent: number): string {
|
|
51
|
+
return `${Math.max(0, Math.min(100, percent))}%`
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
/** 作业进度块:状态徽标 + 阶段 + 进度条 + 日志尾部。 */
|
|
47
55
|
function JobPanel(props: { t: Translate; job: JobDto }): ReactElement {
|
|
48
56
|
const { t, job } = props
|
|
@@ -59,9 +67,7 @@ function JobPanel(props: { t: Translate; job: JobDto }): ReactElement {
|
|
|
59
67
|
<div className="apr-job">
|
|
60
68
|
<div className="apr-jobHead">
|
|
61
69
|
<span className={`apr-pill apr-pill${STATE_CLASS[job.state]}`}>{t(stateKey)}</span>
|
|
62
|
-
<span className="apr-jobTitle">
|
|
63
|
-
{t(job.kind === 'install' ? 'jobInstall' : 'jobSkill')}
|
|
64
|
-
</span>
|
|
70
|
+
<span className="apr-jobTitle">{t(JOB_LABEL[job.kind])}</span>
|
|
65
71
|
<span className="apr-hint">
|
|
66
72
|
{t('jobPhase')} {job.progress.step}/{job.progress.steps}·{job.progress.phase}
|
|
67
73
|
</span>
|
|
@@ -76,7 +82,7 @@ function JobPanel(props: { t: Translate; job: JobDto }): ReactElement {
|
|
|
76
82
|
aria-valuemax={100}
|
|
77
83
|
aria-valuenow={job.progress.percent}
|
|
78
84
|
>
|
|
79
|
-
<div className="apr-barFill" style={{ width:
|
|
85
|
+
<div className="apr-barFill" style={{ width: widthOf(job.progress.percent) }} />
|
|
80
86
|
</div>
|
|
81
87
|
{job.error === null ? null : <p className="apr-error">{job.error}</p>}
|
|
82
88
|
<details className="apr-logBox" open={job.state !== 'ok'}>
|
|
@@ -88,7 +94,7 @@ function JobPanel(props: { t: Translate; job: JobDto }): ReactElement {
|
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
/** 一行状态(键 + 值)。 */
|
|
91
|
-
function StatusRow(props: { label: string; children:
|
|
97
|
+
function StatusRow(props: { label: string; children: ReactNode }): ReactElement {
|
|
92
98
|
return (
|
|
93
99
|
<div className="apr-installRow">
|
|
94
100
|
<dt className="apr-installKey">{props.label}</dt>
|
|
@@ -97,10 +103,16 @@ function StatusRow(props: { label: string; children: React.ReactNode }): ReactEl
|
|
|
97
103
|
)
|
|
98
104
|
}
|
|
99
105
|
|
|
100
|
-
/**
|
|
101
|
-
function
|
|
106
|
+
/** 状态徽标(已就绪 / 未就绪 / 已关闭)。 */
|
|
107
|
+
function Pill(props: { ok: boolean; label: string }): ReactElement {
|
|
108
|
+
return (
|
|
109
|
+
<span className={`apr-pill ${props.ok ? 'apr-pillOk' : 'apr-pillMiss'}`}>{props.label}</span>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** REA 包与 MCP 行状态。 */
|
|
114
|
+
function InstallRows(props: { t: Translate; status: StatusDto }): ReactElement {
|
|
102
115
|
const { t, status } = props
|
|
103
|
-
const skillLabel = status.skill.source === 'updated' ? t('skillUpdated') : t('skillBundled')
|
|
104
116
|
const mcpLabel = !status.mcp.enabled
|
|
105
117
|
? status.config.mcpEnabled
|
|
106
118
|
? t('mcpMissing')
|
|
@@ -109,11 +121,12 @@ function StatusTable(props: { t: Translate; status: StatusDto }): ReactElement {
|
|
|
109
121
|
? t('mcpCustom')
|
|
110
122
|
: t('mcpInstalled')
|
|
111
123
|
return (
|
|
112
|
-
|
|
124
|
+
<>
|
|
113
125
|
<StatusRow label={t('installPackage')}>
|
|
114
|
-
<
|
|
115
|
-
{
|
|
116
|
-
|
|
126
|
+
<Pill
|
|
127
|
+
ok={status.install.installed}
|
|
128
|
+
label={t(status.install.installed ? 'installed' : 'notInstalled')}
|
|
129
|
+
/>
|
|
117
130
|
{status.install.version === null ? null : (
|
|
118
131
|
<span className="apr-mono">{status.install.version}</span>
|
|
119
132
|
)}
|
|
@@ -124,18 +137,6 @@ function StatusTable(props: { t: Translate; status: StatusDto }): ReactElement {
|
|
|
124
137
|
<StatusRow label={t('installPath')}>
|
|
125
138
|
<code className="apr-mono">{status.install.root}</code>
|
|
126
139
|
</StatusRow>
|
|
127
|
-
<StatusRow label={t('skillSource')}>
|
|
128
|
-
<span className="apr-mono">{skillLabel}</span>
|
|
129
|
-
<span className="apr-hint">
|
|
130
|
-
{t('skillFiles')} {status.skill.files}
|
|
131
|
-
{status.skill.version === null ? '' : ` · ${status.skill.version}`}
|
|
132
|
-
</span>
|
|
133
|
-
{status.skill.updatedAt === null ? null : (
|
|
134
|
-
<span className="apr-hint">
|
|
135
|
-
{t('updatedAt')} {status.skill.updatedAt}
|
|
136
|
-
</span>
|
|
137
|
-
)}
|
|
138
|
-
</StatusRow>
|
|
139
140
|
<StatusRow label={t('mcpRow')}>
|
|
140
141
|
<span className="apr-mono">{mcpLabel}</span>
|
|
141
142
|
{status.mcp.command === null ? null : (
|
|
@@ -144,10 +145,125 @@ function StatusTable(props: { t: Translate; status: StatusDto }): ReactElement {
|
|
|
144
145
|
</code>
|
|
145
146
|
)}
|
|
146
147
|
</StatusRow>
|
|
148
|
+
</>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** REA 官方 skill 状态。 */
|
|
153
|
+
function SkillRows(props: { t: Translate; skill: SkillDto; enabled: boolean }): ReactElement {
|
|
154
|
+
const { t, skill, enabled } = props
|
|
155
|
+
const version = skill.version === null ? '' : ` · ${skill.version}`
|
|
156
|
+
return (
|
|
157
|
+
<StatusRow label={t('skillSource')}>
|
|
158
|
+
<Pill ok={enabled} label={t(enabled ? 'enabled' : 'disabled')} />
|
|
159
|
+
<span className="apr-mono">
|
|
160
|
+
{t(skill.source === 'updated' ? 'skillUpdated' : 'skillBundled')}
|
|
161
|
+
</span>
|
|
162
|
+
<span className="apr-hint">
|
|
163
|
+
{t('skillFiles')} {skill.files}
|
|
164
|
+
{version}
|
|
165
|
+
</span>
|
|
166
|
+
{skill.updatedAt === null ? null : (
|
|
167
|
+
<span className="apr-hint">
|
|
168
|
+
{t('updatedAt')} {skill.updatedAt}
|
|
169
|
+
</span>
|
|
170
|
+
)}
|
|
171
|
+
</StatusRow>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** reverse-skill(GitHub 源)状态。 */
|
|
176
|
+
function ReverseRows(props: {
|
|
177
|
+
t: Translate
|
|
178
|
+
reverse: ReverseSkillDto
|
|
179
|
+
enabled: boolean
|
|
180
|
+
}): ReactElement {
|
|
181
|
+
const { t, reverse, enabled } = props
|
|
182
|
+
const active = reverse.activeGroups.length === 0 ? '—' : reverse.activeGroups.join(', ')
|
|
183
|
+
const all = reverse.groups.length === 0 ? '—' : reverse.groups.join(', ')
|
|
184
|
+
return (
|
|
185
|
+
<>
|
|
186
|
+
<StatusRow label={t('reverseSource')}>
|
|
187
|
+
<Pill ok={reverse.installed} label={t(reverse.installed ? 'installed' : 'notInstalled')} />
|
|
188
|
+
<span className="apr-mono">{reverse.repo}</span>
|
|
189
|
+
{reverse.sha === null ? null : (
|
|
190
|
+
<span className="apr-hint">
|
|
191
|
+
{t('reverseCommit')} {reverse.sha.slice(0, 12)}
|
|
192
|
+
{reverse.version === null ? '' : ` · v${reverse.version}`}
|
|
193
|
+
</span>
|
|
194
|
+
)}
|
|
195
|
+
</StatusRow>
|
|
196
|
+
<StatusRow label={t('reverseGroups')}>
|
|
197
|
+
<Pill ok={enabled} label={t(enabled ? 'enabled' : 'disabled')} />
|
|
198
|
+
<span className="apr-mono">{active}</span>
|
|
199
|
+
<span className="apr-hint">
|
|
200
|
+
{t('reverseGroupsHint')} {all}
|
|
201
|
+
</span>
|
|
202
|
+
</StatusRow>
|
|
203
|
+
<StatusRow label={t('reversePath')}>
|
|
204
|
+
<code className="apr-mono">{reverse.tree}</code>
|
|
205
|
+
{reverse.updatedAt === null ? null : (
|
|
206
|
+
<span className="apr-hint">
|
|
207
|
+
{t('updatedAt')} {reverse.updatedAt}
|
|
208
|
+
</span>
|
|
209
|
+
)}
|
|
210
|
+
</StatusRow>
|
|
211
|
+
</>
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** 只读状态表:REA 包 / MCP 行 / REA skill / reverse-skill。 */
|
|
216
|
+
function StatusTable(props: { t: Translate; status: StatusDto }): ReactElement {
|
|
217
|
+
const { t, status } = props
|
|
218
|
+
return (
|
|
219
|
+
<dl className="apr-install">
|
|
220
|
+
<InstallRows t={t} status={status} />
|
|
221
|
+
<SkillRows t={t} skill={status.skill} enabled={status.config.skillEnabled} />
|
|
222
|
+
<ReverseRows t={t} reverse={status.reverse} enabled={status.config.reverseSkillEnabled} />
|
|
147
223
|
</dl>
|
|
148
224
|
)
|
|
149
225
|
}
|
|
150
226
|
|
|
227
|
+
/** 动作按钮行。 */
|
|
228
|
+
function ActionRow(props: {
|
|
229
|
+
t: Translate
|
|
230
|
+
busy: boolean
|
|
231
|
+
running: boolean
|
|
232
|
+
disabled: boolean
|
|
233
|
+
proxyPort: number | null
|
|
234
|
+
onAction(kind: 'install' | 'skill' | 'reverse'): void
|
|
235
|
+
onCancel(): void
|
|
236
|
+
}): ReactElement {
|
|
237
|
+
const { t, busy, running, disabled, proxyPort, onAction, onCancel } = props
|
|
238
|
+
const blocked = disabled || busy || running || proxyPort === null
|
|
239
|
+
return (
|
|
240
|
+
<div className="apr-btnRow">
|
|
241
|
+
<Button
|
|
242
|
+
prefix="apr"
|
|
243
|
+
variant="primary"
|
|
244
|
+
label={t('installAction')}
|
|
245
|
+
disabled={blocked}
|
|
246
|
+
onClick={() => onAction('install')}
|
|
247
|
+
/>
|
|
248
|
+
<Button
|
|
249
|
+
prefix="apr"
|
|
250
|
+
label={t('skillAction')}
|
|
251
|
+
disabled={blocked}
|
|
252
|
+
onClick={() => onAction('skill')}
|
|
253
|
+
/>
|
|
254
|
+
<Button
|
|
255
|
+
prefix="apr"
|
|
256
|
+
label={t('reverseAction')}
|
|
257
|
+
disabled={blocked}
|
|
258
|
+
onClick={() => onAction('reverse')}
|
|
259
|
+
/>
|
|
260
|
+
{running ? (
|
|
261
|
+
<Button prefix="apr" label={t('cancelAction')} disabled={busy} onClick={onCancel} />
|
|
262
|
+
) : null}
|
|
263
|
+
</div>
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
|
|
151
267
|
export interface InstallSectionProps {
|
|
152
268
|
t: Translate
|
|
153
269
|
/** 设置只读(不可写)时禁用全部动作。 */
|
|
@@ -215,7 +331,6 @@ export function InstallSection(props: InstallSectionProps): ReactElement {
|
|
|
215
331
|
[load, t],
|
|
216
332
|
)
|
|
217
333
|
|
|
218
|
-
const blocked = disabled || busy || running === true
|
|
219
334
|
const job = status?.job ?? null
|
|
220
335
|
return (
|
|
221
336
|
<section className="apr-field">
|
|
@@ -235,35 +350,19 @@ export function InstallSection(props: InstallSectionProps): ReactElement {
|
|
|
235
350
|
) : (
|
|
236
351
|
<>
|
|
237
352
|
<StatusTable t={t} status={status} />
|
|
238
|
-
<
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
disabled={blocked || proxyPort === null}
|
|
252
|
-
onClick={() => {
|
|
253
|
-
runAction(() => startJob('skill', proxyPort ?? 0))
|
|
254
|
-
}}
|
|
255
|
-
/>
|
|
256
|
-
{running === true ? (
|
|
257
|
-
<Button
|
|
258
|
-
prefix="apr"
|
|
259
|
-
label={t('cancelAction')}
|
|
260
|
-
disabled={busy}
|
|
261
|
-
onClick={() => {
|
|
262
|
-
runAction(() => cancelJob())
|
|
263
|
-
}}
|
|
264
|
-
/>
|
|
265
|
-
) : null}
|
|
266
|
-
</div>
|
|
353
|
+
<ActionRow
|
|
354
|
+
t={t}
|
|
355
|
+
busy={busy}
|
|
356
|
+
running={running === true}
|
|
357
|
+
disabled={disabled}
|
|
358
|
+
proxyPort={proxyPort}
|
|
359
|
+
onAction={(kind) => {
|
|
360
|
+
runAction(() => startJob(kind, proxyPort ?? 0))
|
|
361
|
+
}}
|
|
362
|
+
onCancel={() => {
|
|
363
|
+
runAction(() => cancelJob())
|
|
364
|
+
}}
|
|
365
|
+
/>
|
|
267
366
|
</>
|
|
268
367
|
)}
|
|
269
368
|
{job === null ? null : <JobPanel t={t} job={job} />}
|
package/src/client/styles.ts
CHANGED
|
@@ -24,6 +24,14 @@ const extraCss = `
|
|
|
24
24
|
.apr-textarea:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
|
|
25
25
|
.apr-textarea:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
|
|
26
26
|
.apr-textarea::placeholder{color:var(--dsw-alias-label-tertiary)}
|
|
27
|
+
.apr-textarea:read-only{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}
|
|
28
|
+
.apr-modes{display:flex;gap:6px;margin:0 0 8px}
|
|
29
|
+
.apr-mode{font:inherit;font-size:12px;line-height:20px;padding:2px 10px;border-radius:999px;cursor:pointer;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover);border:1px solid transparent;transition:color .18s,background .18s,border-color .18s}
|
|
30
|
+
.apr-mode:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
|
|
31
|
+
.apr-mode-on{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-brand-primary)}
|
|
32
|
+
.apr-mode:disabled{cursor:default;opacity:.6}
|
|
33
|
+
.apr-link{font:inherit;font-size:12px;padding:0;border:0;background:none;color:var(--dsw-alias-brand-primary);cursor:pointer;text-decoration:underline}
|
|
34
|
+
.apr-link:disabled{cursor:default;opacity:.6}
|
|
27
35
|
.apr-btnRow{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
|
|
28
36
|
.apr-install{margin:6px 0 0;padding:0;display:flex;flex-direction:column;gap:6px}
|
|
29
37
|
.apr-installRow{display:flex;gap:10px;align-items:baseline;flex-wrap:wrap}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 卡片的「能力开关」区:三个一键开关(REA MCP 行 / REA 官方 skill / reverse-skill
|
|
3
|
+
* 技能包)。三者都是活动字段,保存后本预设换代注册——新建会话按新装配起(REA 工具
|
|
4
|
+
* 与 skill 根的有无),已存在会话保持既有装配。
|
|
5
|
+
*
|
|
6
|
+
* 开关只管「挂不挂」,不管「装没装」:关掉不会删磁盘上的安装副本(更新/回滚仍是
|
|
7
|
+
* 显式动作,见 install.tsx)。
|
|
8
|
+
* @module agent-preset-rea/client/toggles
|
|
9
|
+
*/
|
|
10
|
+
import { CheckRow } from '@dsh-plus/shared/client'
|
|
11
|
+
import type { ReactElement } from 'react'
|
|
12
|
+
|
|
13
|
+
import type { Draft } from './draft.ts'
|
|
14
|
+
import type { Translate } from './i18n.ts'
|
|
15
|
+
|
|
16
|
+
/** 三个开关的键与文案键。 */
|
|
17
|
+
const TOGGLES = [
|
|
18
|
+
{ field: 'mcpEnabled', id: 'apr-toggle-mcp', label: 'toggleMcp', hint: 'toggleMcpHint' },
|
|
19
|
+
{ field: 'skillEnabled', id: 'apr-toggle-skill', label: 'toggleSkill', hint: 'toggleSkillHint' },
|
|
20
|
+
{
|
|
21
|
+
field: 'reverseSkillEnabled',
|
|
22
|
+
id: 'apr-toggle-reverse',
|
|
23
|
+
label: 'toggleReverse',
|
|
24
|
+
hint: 'toggleReverseHint',
|
|
25
|
+
},
|
|
26
|
+
] as const
|
|
27
|
+
|
|
28
|
+
export interface ToggleSectionProps {
|
|
29
|
+
t: Translate
|
|
30
|
+
draft: Draft
|
|
31
|
+
disabled: boolean
|
|
32
|
+
onEdit(patch: Partial<Draft>): void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function ToggleSection(props: ToggleSectionProps): ReactElement {
|
|
36
|
+
const { t, draft, disabled, onEdit } = props
|
|
37
|
+
return (
|
|
38
|
+
<section className="apr-field">
|
|
39
|
+
<div className="apr-head">
|
|
40
|
+
<span className="apr-label">{t('toggleSection')}</span>
|
|
41
|
+
</div>
|
|
42
|
+
{TOGGLES.map((toggle) => (
|
|
43
|
+
<div key={toggle.field}>
|
|
44
|
+
<CheckRow
|
|
45
|
+
prefix="apr"
|
|
46
|
+
id={toggle.id}
|
|
47
|
+
label={t(toggle.label)}
|
|
48
|
+
checked={draft[toggle.field]}
|
|
49
|
+
disabled={disabled}
|
|
50
|
+
onEdit={(checked) => onEdit({ [toggle.field]: checked })}
|
|
51
|
+
/>
|
|
52
|
+
<p className="apr-hint">{t(toggle.hint)}</p>
|
|
53
|
+
</div>
|
|
54
|
+
))}
|
|
55
|
+
</section>
|
|
56
|
+
)
|
|
57
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -5,14 +5,16 @@
|
|
|
5
5
|
* 与 settings 用户层(GUI 配置卡片写入 profile 行级覆盖层,loader 原位提交活动
|
|
6
6
|
* 引用,消费端 `unwrapVolatile` 现取即热)。
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* **活动字段(`.volatile()`)共六个**,全部在配置卡片里有编辑器(见 src/client/card.tsx):
|
|
9
|
+
* `personaPrefixMode` / `personaPrefix`(提示词来源与正文)、`proxyPort`(安装代理)、
|
|
10
|
+
* `mcpEnabled` / `skillEnabled` / `reverseSkillEnabled`(三个能力开关)——保存即热更
|
|
11
|
+
* (预设换代,新建会话生效)。其余字段是**部署方平面**(受管目录、版本钉、skill 组、
|
|
12
|
+
* 超时等运维旋钮),由 `cordis.patch.yml` 整体覆盖,GUI 不提供入口。
|
|
12
13
|
*
|
|
13
|
-
* **persona
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* **persona 默认为随包内置提示词**(见 src/persona-prefix.ts):`personaPrefixMode`
|
|
15
|
+
* 默认 `default`,`persona` 行写入内置文本;切到 `custom` 才由 `personaPrefix` 供文本。
|
|
16
|
+
* 该行只遮蔽**部署级 persona 段**(`deployment:persona-prefix`,order 0),官方装配
|
|
17
|
+
* (harness 身份、工具指引、项目指令、运行时上下文)不受影响。
|
|
16
18
|
*
|
|
17
19
|
* 预设 id 固定为 `rea`(注册表身份,不开放覆盖——改 id 等于换预设,
|
|
18
20
|
* default/selectedDefault 等引用都会落空)。
|
|
@@ -23,6 +25,7 @@ import type { UnwrapVolatile } from '@dsh-plus/shared'
|
|
|
23
25
|
|
|
24
26
|
import { INPUT_SCHEMA_PROFILES, OUTPUT_SCHEMA_PROFILES, REA_PINNED_VERSION } from './mcp.ts'
|
|
25
27
|
import { SETTINGS_NS as NS_LITERAL } from './ns.ts'
|
|
28
|
+
import { DEFAULT_PERSONA_PREFIX } from './persona-prefix.ts'
|
|
26
29
|
|
|
27
30
|
/** settings 命名空间(字面量即合法命名空间;配置卡片与运行期读取同一份)。 */
|
|
28
31
|
export const SETTINGS_NS = NS_LITERAL
|
|
@@ -37,20 +40,67 @@ export const Config = z.object({
|
|
|
37
40
|
.string()
|
|
38
41
|
.description('预设说明(设置 → Agent 预设卡片描述)')
|
|
39
42
|
.default(
|
|
40
|
-
'逆向工程预设:官方 standard 全量行 + 仅本预设可见的 REA MCP 客户端(mcp__rea__*)与随包 REA 官方 skill;persona
|
|
43
|
+
'逆向工程预设:官方 standard 全量行 + 仅本预设可见的 REA MCP 客户端(mcp__rea__*)与随包 REA 官方 skill;persona 默认取随包内置提示词,工具指引沿用官方装配。',
|
|
41
44
|
),
|
|
42
45
|
order: z.number().description('预设名册排序位').default(6),
|
|
46
|
+
personaPrefixMode: z
|
|
47
|
+
.union(['default', 'custom'])
|
|
48
|
+
.description(
|
|
49
|
+
'persona 前缀来源:default = 随包内置提示词(src/persona-prefix.ts),custom = 取 personaPrefix 的用户文本',
|
|
50
|
+
)
|
|
51
|
+
.default('default')
|
|
52
|
+
.volatile(),
|
|
43
53
|
personaPrefix: z
|
|
44
54
|
.string()
|
|
45
55
|
.description(
|
|
46
|
-
'persona
|
|
56
|
+
'persona 前缀正文(system-prompt 的 deployment:persona-prefix 段);仅 personaPrefixMode=custom 时生效,空值回落内置提示词',
|
|
47
57
|
)
|
|
48
|
-
.default(
|
|
58
|
+
.default(DEFAULT_PERSONA_PREFIX)
|
|
49
59
|
.volatile(),
|
|
60
|
+
capabilityProtocol: z
|
|
61
|
+
.string()
|
|
62
|
+
.description(
|
|
63
|
+
'追加到 persona 前缀正文末尾的「能力选用协议」段(任务驱动挑选技能与 MCP;部署方平面,空串 = 不追加)',
|
|
64
|
+
)
|
|
65
|
+
.default(''),
|
|
66
|
+
subagentDirective: z
|
|
67
|
+
.string()
|
|
68
|
+
.description(
|
|
69
|
+
'追加到 persona 前缀正文末尾的「子代理指令继承」段(派发 brief 时须浓缩继承本节规则;部署方平面,空串 = 不追加)',
|
|
70
|
+
)
|
|
71
|
+
.default(''),
|
|
50
72
|
mcpEnabled: z
|
|
51
73
|
.boolean()
|
|
52
74
|
.description('是否挂 REA MCP 客户端行(false = 只保留预设其余能力)')
|
|
53
|
-
.default(true)
|
|
75
|
+
.default(true)
|
|
76
|
+
.volatile(),
|
|
77
|
+
skillEnabled: z
|
|
78
|
+
.boolean()
|
|
79
|
+
.description('是否把 REA 官方 skill 根挂进本预设(false = 模型只看到其余 skill 根)')
|
|
80
|
+
.default(true)
|
|
81
|
+
.volatile(),
|
|
82
|
+
reverseSkillEnabled: z
|
|
83
|
+
.boolean()
|
|
84
|
+
.description(
|
|
85
|
+
'是否挂 zhaoxuya520/reverse-skill 技能包(false = 从本预设的 skill 根里摘掉,安装副本仍在)',
|
|
86
|
+
)
|
|
87
|
+
.default(true)
|
|
88
|
+
.volatile(),
|
|
89
|
+
reverseSkillRepo: z
|
|
90
|
+
.string()
|
|
91
|
+
.pattern(/^[\w.-]+\/[\w.-]+$/)
|
|
92
|
+
.description('reverse-skill 仓库(<owner>/<repo>;安装/手动更新都从这里拉)')
|
|
93
|
+
.default('zhaoxuya520/reverse-skill'),
|
|
94
|
+
reverseSkillRef: z
|
|
95
|
+
.string()
|
|
96
|
+
.description('reverse-skill 的 ref(分支/标签/提交;留空 = 仓库默认分支的最新提交)')
|
|
97
|
+
.default(''),
|
|
98
|
+
reverseSkillGroups: z
|
|
99
|
+
.array(z.string())
|
|
100
|
+
.description(
|
|
101
|
+
'reverse-skill 里挂进本预设的技能组(相对安装树根;默认只挂主包根 skills——含路由技能与全部模块技能,CTF 侧车等组按需追加)',
|
|
102
|
+
)
|
|
103
|
+
.default(['skills']),
|
|
54
104
|
mcpServerName: z
|
|
55
105
|
.string()
|
|
56
106
|
.pattern(/^[A-Za-z0-9_-]{1,32}$/)
|
package/src/definition.ts
CHANGED
|
@@ -7,11 +7,18 @@
|
|
|
7
7
|
* tests/upstream-mirror.test.ts 会逼出对齐。本预设不丢任何上游行(与 lean 相反)。
|
|
8
8
|
*
|
|
9
9
|
* 增量仅三处,测试按 {@link ADDED_ROWS} / {@link CONFIG_DELTAS} 白名单放行:
|
|
10
|
-
* 1. `persona` 行 `prefix`
|
|
11
|
-
*
|
|
10
|
+
* 1. `persona` 行 `prefix` 取 `resolvePersonaPrefix()`(默认随包内置提示词;
|
|
11
|
+
* `personaPrefixMode=custom` 时才取用户文本);
|
|
12
|
+
* 2. `skill-filesystem` 行加 `customSkillDirs`(REA skill 源与 reverse-skill 技能组,
|
|
13
|
+
* 各受一个活动开关控制,另加部署方 `extraSkillDirs`);
|
|
12
14
|
* 3. 新增 `mcp-rea` 行:官方 MCP 客户端连 REA stdio 服务,落在本预设作用域内;
|
|
13
15
|
* **未安装 REA 时该行 `disabled`**(不 spawn npx,见 mcp.ts 的说明)。
|
|
14
16
|
*
|
|
17
|
+
* 注入点固定在 `persona` 行:官方 `dsh-persona` 为该行注册 `deployment:persona-prefix`
|
|
18
|
+
* 段(order 0,早于全部工具指引段),且 README 明确这一行是 preset 改身份的**唯一**
|
|
19
|
+
* 入口——部署级 `system-prompt` 行归宿主平面,本预设不挂、也挂不上(作用域外挂载会与
|
|
20
|
+
* 注册表自身的 persona 注册相撞并报错)。
|
|
21
|
+
*
|
|
15
22
|
* {@link RuntimeSnapshot} 是运行期事实(受管安装与 skill 来源),由 manager 现探后
|
|
16
23
|
* 传入;不传即「未安装 + 随包 skill」,测试据此断言两种形态。
|
|
17
24
|
*
|
|
@@ -23,8 +30,10 @@ import type { PresetDefinition } from '@deepseek-ai/dsh-agent-preset-registry'
|
|
|
23
30
|
import type { AgentPresetReaConfig } from './config.ts'
|
|
24
31
|
import type { RuntimeSnapshot } from './manager.ts'
|
|
25
32
|
import { DEFAULT_MCP_COMMAND, reaMcpArgs, reaMcpEnv, reaMcpLaunch } from './mcp.ts'
|
|
33
|
+
import { resolvePersonaPrefix } from './persona-prefix.ts'
|
|
26
34
|
import { PLAN_MODE_SECTION } from './prompt.ts'
|
|
27
|
-
import {
|
|
35
|
+
import { emptyReverseState } from './reverse.ts'
|
|
36
|
+
import { BUNDLED_SKILL_ROOT, presetSkillDirs } from './skill.ts'
|
|
28
37
|
|
|
29
38
|
/** 预设注册表身份(改 id 等于换预设,default 等引用都会落空)。 */
|
|
30
39
|
export const REA_PRESET_ID = 'rea'
|
|
@@ -32,6 +41,9 @@ export const REA_PRESET_ID = 'rea'
|
|
|
32
41
|
/** 相对上游 `standard` 新增的行 id(镜像测试按此白名单放行自造行)。 */
|
|
33
42
|
export const ADDED_ROWS: readonly string[] = ['mcp-rea']
|
|
34
43
|
|
|
44
|
+
/** 声明必须具备的组行 id(缺一即注册前失败,见 {@link assertRequiredRows})。 */
|
|
45
|
+
const REQUIRED_GROUP_ROWS: readonly string[] = ['planning', 'compaction', 'delegation']
|
|
46
|
+
|
|
35
47
|
/**
|
|
36
48
|
* 已镜像行 → 允许偏离上游的字段名(其余字段必须逐字一致)。
|
|
37
49
|
* 删除某条等于把该行改回上游原文;改动上游不存在的字段会被测试拦下。
|
|
@@ -41,6 +53,25 @@ export const CONFIG_DELTAS: Readonly<Record<string, readonly string[]>> = {
|
|
|
41
53
|
'skill-filesystem': ['customSkillDirs'],
|
|
42
54
|
}
|
|
43
55
|
|
|
56
|
+
/**
|
|
57
|
+
* 最小(或极简)预设 id 判定:命中时注入文本需做减法适配。
|
|
58
|
+
*
|
|
59
|
+
* 判定面与上游预设命名惯例一致(`minimal` / `sdk-minimal` / `<x>/minimal` /
|
|
60
|
+
* 含 `preset-minimal`),不引入自造 id 概念。
|
|
61
|
+
*/
|
|
62
|
+
export function isMinimalPresetId(id: string | undefined): boolean {
|
|
63
|
+
const value = String(id ?? '')
|
|
64
|
+
.trim()
|
|
65
|
+
.toLowerCase()
|
|
66
|
+
if (value === '') return false
|
|
67
|
+
return (
|
|
68
|
+
value === 'minimal' ||
|
|
69
|
+
value === 'sdk-minimal' ||
|
|
70
|
+
value.endsWith('/minimal') ||
|
|
71
|
+
value.includes('preset-minimal')
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
44
75
|
/** 平台门控表达式:与上游 `standard` 预设逐字相同,Windows 上由 pwsh 顶替 bash。 */
|
|
45
76
|
const WINDOWS_ONLY = { __jsExpr: "process.platform === 'win32'" } as const
|
|
46
77
|
const POSIX_ONLY = { __jsExpr: "process.platform !== 'win32'" } as const
|
|
@@ -58,6 +89,7 @@ export function emptyRuntime(): RuntimeSnapshot {
|
|
|
58
89
|
files: 0,
|
|
59
90
|
updatedAt: null,
|
|
60
91
|
},
|
|
92
|
+
reverse: emptyReverseState(),
|
|
61
93
|
}
|
|
62
94
|
}
|
|
63
95
|
|
|
@@ -70,12 +102,13 @@ type Row = PresetDefinition['plugins'][number]
|
|
|
70
102
|
*/
|
|
71
103
|
function baseRows(config: AgentPresetReaConfig, runtime: RuntimeSnapshot): Row[] {
|
|
72
104
|
return [
|
|
73
|
-
// 身份:沿用官方装配(非 complete
|
|
74
|
-
//
|
|
105
|
+
// 身份:沿用官方装配(非 complete、保留运行时上下文)。前缀来源见
|
|
106
|
+
// resolvePersonaPrefix:默认随包内置提示词,custom 模式才取用户文本。
|
|
107
|
+
// 该行注册 deployment:persona-prefix 段(order 0),遮蔽部署级 persona 而不动工具指引。
|
|
75
108
|
{
|
|
76
109
|
id: 'persona',
|
|
77
110
|
name: '@deepseek-ai/dsh-persona',
|
|
78
|
-
config: { prefix: config
|
|
111
|
+
config: { prefix: resolvePersonaPrefix(config) },
|
|
79
112
|
},
|
|
80
113
|
// 项目指令(AGENTS.md)与逐步时钟上下文(默认 10 分钟一条)。
|
|
81
114
|
{
|
|
@@ -102,10 +135,7 @@ function baseRows(config: AgentPresetReaConfig, runtime: RuntimeSnapshot): Row[]
|
|
|
102
135
|
id: 'skill-filesystem',
|
|
103
136
|
name: '@deepseek-ai/dsh-skill-filesystem',
|
|
104
137
|
config: {
|
|
105
|
-
customSkillDirs:
|
|
106
|
-
config,
|
|
107
|
-
runtime.skill.source === 'updated' ? runtime.skill.root : null,
|
|
108
|
-
),
|
|
138
|
+
customSkillDirs: presetSkillDirs(config, runtime),
|
|
109
139
|
},
|
|
110
140
|
},
|
|
111
141
|
{ id: 'tool-skill', name: '@deepseek-ai/dsh-tool-skill' },
|
|
@@ -220,11 +250,28 @@ function orchestrationRows(config: AgentPresetReaConfig, runtime: RuntimeSnapsho
|
|
|
220
250
|
},
|
|
221
251
|
},
|
|
222
252
|
{ id: 'present', name: '@deepseek-ai/dsh-tool-present' },
|
|
223
|
-
//
|
|
253
|
+
// 宿主插件管理与插件市场:上游 standard 里同样是 disabled 行。
|
|
224
254
|
{ id: 'tool-plugin-manager', name: '@deepseek-ai/dsh-plugin-manager/tools', disabled: true },
|
|
255
|
+
{ id: 'tool-plugin-market', name: '@deepseek-ai/dsh-plugin-market/tools', disabled: true },
|
|
225
256
|
]
|
|
226
257
|
}
|
|
227
258
|
|
|
259
|
+
/**
|
|
260
|
+
* 声明必备行自检:功能面缺行时在注册前失败,而不是静默少能力。
|
|
261
|
+
*
|
|
262
|
+
* 与 tests/upstream-mirror.test.ts 的分工:夹具测试守「偏离上游」,
|
|
263
|
+
* 这里守「本预设必须自带的组行与增量行」——上游升版把某组行改名或
|
|
264
|
+
* 本文件误删一行时,注册表挂载前就能看到明确错误。
|
|
265
|
+
* @param rows - 展开前后的顶层行清单。
|
|
266
|
+
*/
|
|
267
|
+
function assertRequiredRows(rows: readonly Row[]): void {
|
|
268
|
+
const ids = new Set(rows.map((row) => String(row.id)))
|
|
269
|
+
const missing = [...REQUIRED_GROUP_ROWS, ...ADDED_ROWS].filter((id) => !ids.has(id))
|
|
270
|
+
if (missing.length > 0) {
|
|
271
|
+
throw new Error(`rea 预设声明缺少必备行:${missing.join(', ')}`)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
228
275
|
/**
|
|
229
276
|
* 由配置 + 运行期事实拼出注册表声明。
|
|
230
277
|
* @param config - 已验证的插件配置。
|
|
@@ -235,11 +282,13 @@ export function reaPresetDefinition(
|
|
|
235
282
|
config: AgentPresetReaConfig,
|
|
236
283
|
runtime: RuntimeSnapshot = emptyRuntime(),
|
|
237
284
|
): PresetDefinition {
|
|
285
|
+
const plugins = [...baseRows(config, runtime), ...orchestrationRows(config, runtime)]
|
|
286
|
+
assertRequiredRows(plugins)
|
|
238
287
|
return {
|
|
239
288
|
id: REA_PRESET_ID,
|
|
240
289
|
name: config.name,
|
|
241
290
|
description: config.description,
|
|
242
291
|
order: config.order,
|
|
243
|
-
plugins
|
|
292
|
+
plugins,
|
|
244
293
|
}
|
|
245
294
|
}
|