dsh-advisor-plugin 0.2.2 → 0.2.4
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/README.md +11 -1
- package/lib/client/AdvisorCard.d.ts +3 -4
- package/lib/client/AdvisorCard.js +41 -23
- package/lib/client/controller.d.ts +5 -0
- package/lib/client/controller.js +13 -1
- package/lib/client/index.js +174 -107
- package/lib/client/locales.d.ts +7 -0
- package/lib/client/locales.js +14 -0
- package/lib/config.d.ts +4 -2
- package/lib/config.js +6 -2
- package/package.json +11 -11
- package/src/client/AdvisorCard.tsx +168 -109
- package/src/client/controller.ts +17 -1
- package/src/client/locales.ts +14 -0
- package/src/config.ts +6 -2
package/README.md
CHANGED
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
|
|
21
21
|
## 特性
|
|
22
22
|
|
|
23
|
+
- **总开关**:设置页一键启用/关闭;关闭时不注册 advisor 工具与升级守则,已保存的模型配置保留。
|
|
24
|
+
- **可折叠配置**:模型与推理档位、巡逻模式两个区块可分别收起/展开。
|
|
23
25
|
- **零参数 `advisor()` 工具**:调用即自动转发整段会话,模型不需要组织上下文。
|
|
24
26
|
- **plan / correction / stop 契约**:审查模型只做判断,不碰代码、不直接改状态。
|
|
25
27
|
- **compaction 感知**:转发的是 `session.deriveMessages()` 的真实模型可见面,而不是过期原始历史。
|
|
@@ -116,6 +118,7 @@ dsh plugin --profile web add github:leeyoung1/dsh-advisor-plugin
|
|
|
116
118
|
|
|
117
119
|
```yaml
|
|
118
120
|
advisor:
|
|
121
|
+
enabled: true
|
|
119
122
|
provider: example-provider
|
|
120
123
|
model: reviewer-model
|
|
121
124
|
effort: high
|
|
@@ -166,10 +169,17 @@ agent/request ──每 N 步──▶ 快照 ──▶ 审查模型
|
|
|
166
169
|
|
|
167
170
|
## 依赖要求
|
|
168
171
|
|
|
169
|
-
- Node.js
|
|
172
|
+
- Node.js `^22.19` 或 `>=24`
|
|
170
173
|
- DeepSeek Harness(web profile)
|
|
171
174
|
- 已配置至少一个可用的 provider / model 作为审查模型
|
|
172
175
|
|
|
176
|
+
## 兼容性
|
|
177
|
+
|
|
178
|
+
- **DSH 版本**:peer 范围覆盖 `0.1.0-rc.6`、`0.1.1-rc.2`、`0.1.2-rc.1`、`0.1.3-alpha.1`;已在 `0.1.1-rc.2` 上实机验证。
|
|
179
|
+
- **配置向后兼容**:旧配置没有 `enabled` 字段时默认 `true`,不影响升级。
|
|
180
|
+
- **关闭总开关**:`enabled: false` 时保留已保存的模型配置,但不注册工具与升级守则。
|
|
181
|
+
- **安装方式**:请使用 `dsh plugin --profile web add dsh-advisor-plugin`;裸 `npm install` 会撞 DSH 官方包的 peer 依赖,这不是插件本身的问题。
|
|
182
|
+
|
|
173
183
|
开发验证:
|
|
174
184
|
|
|
175
185
|
```bash
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AdvisorCard ——
|
|
2
|
+
* AdvisorCard —— 设置页“插件”栏里的 Advisor 卡片(浏览器半)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* 本组件纯渲染,样式内联(不依赖 css module 构建)。
|
|
4
|
+
* 交互:总开关 + 可折叠的模型/档位区块与巡逻区块;数据与动作全部由
|
|
5
|
+
* controller 通过 slot inject 面注入;本组件纯渲染,样式内联。
|
|
7
6
|
*/
|
|
8
7
|
import type { AdvisorCardFace, AdvisorCardState } from './controller.js';
|
|
9
8
|
import type { LocaleKey } from './locales.js';
|
|
@@ -1,23 +1,32 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* AdvisorCard —— 设置页“插件”栏里的 Advisor 卡片(浏览器半)。
|
|
4
|
+
*
|
|
5
|
+
* 交互:总开关 + 可折叠的模型/档位区块与巡逻区块;数据与动作全部由
|
|
6
|
+
* controller 通过 slot inject 面注入;本组件纯渲染,样式内联。
|
|
7
|
+
*/
|
|
8
|
+
import { useState } from 'react';
|
|
9
|
+
/** 通用开关 */
|
|
10
|
+
function ToggleSwitch(props) {
|
|
11
|
+
return (_jsx("button", { type: "button", role: "switch", "aria-checked": props.checked, "aria-label": props.label, disabled: props.disabled, onClick: props.onToggle, style: {
|
|
12
|
+
...style.button,
|
|
13
|
+
width: 40, height: 22, borderRadius: 11, padding: 0, position: 'relative',
|
|
14
|
+
background: props.checked ? '#16a34a' : 'rgba(128,128,128,.4)', border: 'none',
|
|
15
|
+
}, children: _jsx("span", { style: {
|
|
16
|
+
position: 'absolute', top: 2, left: props.checked ? 20 : 2,
|
|
17
|
+
width: 18, height: 18, borderRadius: 9, background: '#fff', transition: 'left .15s',
|
|
18
|
+
} }) }));
|
|
19
|
+
}
|
|
20
|
+
/** 可折叠区块:点击标题行展开/收起 */
|
|
21
|
+
function Section(props) {
|
|
22
|
+
const [open, setOpen] = useState(props.defaultOpen ?? false);
|
|
23
|
+
return (_jsxs("div", { style: style.section, children: [_jsxs("button", { type: "button", style: style.sectionHead, "aria-expanded": open, onClick: () => setOpen(v => !v), children: [_jsx("span", { style: style.sectionTitle, children: props.title }), props.subtitle === undefined ? null : _jsx("span", { style: style.sectionSubtitle, children: props.subtitle }), _jsx("span", { style: style.sectionState, children: open ? props.t('sectionCollapse') : props.t('sectionExpand') })] }), open ? _jsx("div", { style: style.sectionBody, children: props.children }) : null] }));
|
|
24
|
+
}
|
|
2
25
|
/** 巡逻模式区块(自动检测跑偏) */
|
|
3
26
|
function PatrolSection(props) {
|
|
4
27
|
const { t } = props;
|
|
5
|
-
return (_jsxs("div", { style: {
|
|
6
|
-
|
|
7
|
-
width: 40, height: 22, borderRadius: 11, padding: 0, position: 'relative',
|
|
8
|
-
background: props.enabled ? '#16a34a' : 'rgba(128,128,128,.4)', border: 'none',
|
|
9
|
-
}, children: _jsx("span", { style: {
|
|
10
|
-
position: 'absolute', top: 2, left: props.enabled ? 20 : 2,
|
|
11
|
-
width: 18, height: 18, borderRadius: 9, background: '#fff', transition: 'left .15s',
|
|
12
|
-
} }) })] }), props.enabled
|
|
13
|
-
? (_jsxs("div", { children: [_jsxs("div", { style: style.effortRow, children: [_jsx("label", { htmlFor: "advisor-patrol-steps", children: t('patrolEverySteps') }), _jsx("input", { id: "advisor-patrol-steps", type: "number", min: 2, max: 500, value: props.everySteps, disabled: props.disabled, onChange: (event) => { props.setPatrolEverySteps(Number(event.target.value)); }, style: { width: 64 } })] }), _jsxs("div", { style: style.effortRow, children: [_jsx("label", { htmlFor: "advisor-patrol-immune", children: t('patrolImmuneTurns') }), _jsx("input", { id: "advisor-patrol-immune", type: "number", min: 0, max: 20, value: props.immuneTurns, disabled: props.disabled, onChange: (event) => { props.setPatrolImmuneTurns(Number(event.target.value)); }, style: { width: 64 } })] }), _jsxs("div", { style: { ...style.effortRow, marginTop: 8 }, children: [_jsx("span", { children: t('investigateToggle') }), _jsx("button", { type: "button", role: "switch", "aria-checked": props.investigate, "aria-label": t('investigateToggle'), disabled: props.disabled, onClick: props.toggleInvestigate, style: {
|
|
14
|
-
...style.button,
|
|
15
|
-
width: 40, height: 22, borderRadius: 11, padding: 0, position: 'relative',
|
|
16
|
-
background: props.investigate ? '#16a34a' : 'rgba(128,128,128,.4)', border: 'none',
|
|
17
|
-
}, children: _jsx("span", { style: {
|
|
18
|
-
position: 'absolute', top: 2, left: props.investigate ? 20 : 2,
|
|
19
|
-
width: 18, height: 18, borderRadius: 9, background: '#fff', transition: 'left .15s',
|
|
20
|
-
} }) })] }), _jsx("p", { style: { ...style.notice, marginTop: -2 }, children: t('investigateHint') })] }))
|
|
28
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 10 }, children: [_jsx("p", { style: style.description, children: t('patrolDescription') }), _jsxs("div", { style: style.effortRow, children: [_jsx("span", { children: t('patrolToggle') }), _jsx(ToggleSwitch, { checked: props.enabled, disabled: props.disabled, label: t('patrolToggle'), onToggle: props.togglePatrol })] }), props.enabled
|
|
29
|
+
? (_jsxs("div", { children: [_jsxs("div", { style: style.effortRow, children: [_jsx("label", { htmlFor: "advisor-patrol-steps", children: t('patrolEverySteps') }), _jsx("input", { id: "advisor-patrol-steps", type: "number", min: 2, max: 500, value: props.everySteps, disabled: props.disabled, onChange: (event) => { props.setPatrolEverySteps(Number(event.target.value)); }, style: { width: 64 } })] }), _jsxs("div", { style: style.effortRow, children: [_jsx("label", { htmlFor: "advisor-patrol-immune", children: t('patrolImmuneTurns') }), _jsx("input", { id: "advisor-patrol-immune", type: "number", min: 0, max: 20, value: props.immuneTurns, disabled: props.disabled, onChange: (event) => { props.setPatrolImmuneTurns(Number(event.target.value)); }, style: { width: 64 } })] }), _jsxs("div", { style: { ...style.effortRow, marginTop: 8 }, children: [_jsx("span", { children: t('investigateToggle') }), _jsx(ToggleSwitch, { checked: props.investigate, disabled: props.disabled, label: t('investigateToggle'), onToggle: props.toggleInvestigate })] }), _jsx("p", { style: { ...style.notice, marginTop: -2 }, children: t('investigateHint') })] }))
|
|
21
30
|
: null, _jsx("p", { style: { ...style.notice, marginTop: -2 }, children: t('patrolEveryStepsHint') })] }));
|
|
22
31
|
}
|
|
23
32
|
const EFFORT_OPTIONS = ['', 'off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
|
|
@@ -41,20 +50,29 @@ const style = {
|
|
|
41
50
|
secondary: { background: 'transparent', border: '1px solid rgba(128,128,128,.5)' },
|
|
42
51
|
status: { fontSize: 12 },
|
|
43
52
|
list: { maxHeight: 260, overflowY: 'auto', border: '1px solid rgba(128,128,128,.25)', borderRadius: 6, padding: '4px 10px' },
|
|
53
|
+
switchRow: { display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, marginTop: 2 },
|
|
54
|
+
switchLabel: { fontWeight: 600 },
|
|
55
|
+
section: { border: '1px solid rgba(128,128,128,.25)', borderRadius: 8, overflow: 'hidden' },
|
|
56
|
+
sectionHead: { display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 10px', background: 'rgba(128,128,128,.06)', border: 'none', font: 'inherit', textAlign: 'left', cursor: 'pointer' },
|
|
57
|
+
sectionTitle: { fontSize: 13, fontWeight: 600 },
|
|
58
|
+
sectionSubtitle: { fontSize: 11, opacity: 0.65, flex: 1 },
|
|
59
|
+
sectionState: { fontSize: 11, opacity: 0.65, marginLeft: 'auto' },
|
|
60
|
+
sectionBody: { padding: 10, display: 'flex', flexDirection: 'column', gap: 8 },
|
|
44
61
|
};
|
|
45
62
|
export function AdvisorCard(props) {
|
|
46
63
|
const state = props.useAdvisorCard(s => s);
|
|
64
|
+
const enabled = state.enabled;
|
|
47
65
|
// 档位选项:目录声明了支持面 → 只列支持的;未声明 → 全档位(host 自动降级兜底)
|
|
48
66
|
const effortOptions = state.efforts.known
|
|
49
67
|
? ['', ...state.efforts.levels]
|
|
50
68
|
: EFFORT_OPTIONS;
|
|
51
69
|
const disabled = !state.writable || state.saving;
|
|
52
|
-
const armed = state.effective.provider !== '' && state.effective.model !== '';
|
|
53
|
-
return (_jsxs("div", { style: style.card, children: [_jsx("h3", { style: style.title, children: props.t('title') }), _jsx("p", { style: style.description, children: props.t('description') }), _jsxs("p", { style: style.current, children: [props.t('armed'), "\uFF1A", armed
|
|
70
|
+
const armed = enabled && state.effective.provider !== '' && state.effective.model !== '';
|
|
71
|
+
return (_jsxs("div", { style: style.card, children: [_jsx("h3", { style: style.title, children: props.t('title') }), _jsx("p", { style: style.description, children: props.t('description') }), _jsxs("div", { style: style.switchRow, children: [_jsx("span", { style: style.switchLabel, children: props.t('enabledToggle') }), _jsx(ToggleSwitch, { checked: enabled, disabled: disabled, label: props.t('enabledToggle'), onToggle: props.toggleEnabled })] }), _jsx("p", { style: style.notice, children: enabled ? props.t('enabledHintOn') : props.t('enabledHintOff') }), _jsxs("p", { style: style.current, children: [props.t('armed'), "\uFF1A", armed
|
|
54
72
|
? _jsx("strong", { children: `${state.effective.provider}/${state.effective.model}${state.effective.effort === '' ? '' : ` (${state.effective.effort})`}` })
|
|
55
|
-
: props.t('offCurrent')] }), !state.writable ? _jsx("p", { style: style.notice, children: props.t('readonly') }) : null, state.catalogStatus === 'loading' ? _jsx("p", { style: style.notice, children: props.t('loading') }) : null, state.catalogStatus === 'error'
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
73
|
+
: props.t('offCurrent')] }), !state.writable ? _jsx("p", { style: style.notice, children: props.t('readonly') }) : null, _jsxs(Section, { t: props.t, title: props.t('modelSection'), subtitle: props.t('modelSectionHint'), defaultOpen: true, children: [state.catalogStatus === 'loading' ? _jsx("p", { style: style.notice, children: props.t('loading') }) : null, state.catalogStatus === 'error'
|
|
74
|
+
? (_jsxs("div", { style: style.error, children: [_jsx("span", { children: props.t('loadFailed') }), _jsx("button", { type: "button", style: { ...style.button, ...style.secondary }, disabled: disabled, onClick: props.retryCatalog, children: props.t('retry') })] }))
|
|
75
|
+
: null, state.catalogPartial ? _jsx("p", { style: style.notice, children: props.t('partial') }) : null, state.groups.length > 0
|
|
76
|
+
? (_jsxs("div", { children: [_jsx("p", { style: style.notice, children: props.t('choose') }), _jsx("div", { style: style.list, children: state.groups.map(group => (_jsxs("div", { children: [_jsx("div", { style: style.groupTitle, children: group.name }), group.candidates.map(candidate => (_jsxs("label", { style: style.row, children: [_jsx("input", { type: "radio", name: "advisor-route", checked: candidate.selected, disabled: disabled, onChange: () => { props.pickRoute(candidate.key); } }), _jsxs("span", { style: style.modelName, children: [_jsx("span", { children: candidate.modelName }), _jsx("span", { style: style.route, children: `${candidate.provider}/${candidate.model}` })] }), !candidate.available ? _jsx("span", { style: style.badge, children: props.t('unavailable') }) : null] }, candidate.key)))] }, group.id))) })] }))
|
|
77
|
+
: state.catalogStatus === 'ready' ? _jsx("p", { style: style.notice, children: props.t('empty') }) : null, _jsxs("div", { style: style.effortRow, children: [_jsx("label", { htmlFor: "advisor-effort", children: props.t('effort') }), _jsx("select", { id: "advisor-effort", value: state.effective.effort, disabled: disabled, onChange: (event) => { props.setEffort(event.target.value); }, children: effortOptions.map(option => (_jsx("option", { value: option, children: option === '' ? props.t('effortDefault') : option }, option))) })] }), _jsx("p", { style: { ...style.notice, marginTop: -4 }, children: state.efforts.known ? props.t('effortHintKnown') : props.t('effortHint') })] }), _jsx(Section, { t: props.t, title: props.t('patrolTitle'), defaultOpen: false, children: _jsx(PatrolSection, { ...props, enabled: state.patrol.enabled, everySteps: state.patrol.everySteps, immuneTurns: state.patrol.immuneTurns, investigate: state.patrol.investigate, disabled: disabled }) }), _jsxs("div", { style: style.actions, children: [_jsx("button", { type: "button", style: { ...style.button, ...style.primary }, disabled: disabled || !state.dirty, onClick: props.save, children: props.t('save') }), _jsx("button", { type: "button", style: { ...style.button, ...style.secondary }, disabled: disabled || !state.dirty, onClick: props.discard, children: props.t('discard') }), state.saving ? _jsx("span", { style: style.status, children: props.t('saving') }) : null, state.failed ? _jsx("span", { style: { ...style.status, color: '#b91c1c' }, children: props.t('saveFailed') }) : null] })] }));
|
|
60
78
|
}
|
|
@@ -71,6 +71,8 @@ export interface AdvisorCardState {
|
|
|
71
71
|
dirty: boolean;
|
|
72
72
|
catalogStatus: 'idle' | 'loading' | 'ready' | 'error';
|
|
73
73
|
catalogPartial: boolean;
|
|
74
|
+
/** 总开关的生效值(draft 优先) */
|
|
75
|
+
enabled: boolean;
|
|
74
76
|
/** 生效值(draft 优先,其次已保存值) */
|
|
75
77
|
effective: AdvisorRoute;
|
|
76
78
|
/** 巡逻模式的生效值(同 draft 优先) */
|
|
@@ -96,6 +98,7 @@ export interface AdvisorCardFace {
|
|
|
96
98
|
advisorCard: SnapshotStore<AdvisorCardState>;
|
|
97
99
|
};
|
|
98
100
|
pickRoute: (key: string) => void;
|
|
101
|
+
toggleEnabled: () => void;
|
|
99
102
|
setEffort: (effort: string) => void;
|
|
100
103
|
togglePatrol: () => void;
|
|
101
104
|
setPatrolEverySteps: (steps: number) => void;
|
|
@@ -120,6 +123,7 @@ export declare function currentEffortSupport(groups: readonly CatalogGroup[], ro
|
|
|
120
123
|
levels: readonly string[];
|
|
121
124
|
};
|
|
122
125
|
export interface AdvisorSection {
|
|
126
|
+
enabled?: boolean;
|
|
123
127
|
provider?: string;
|
|
124
128
|
model?: string;
|
|
125
129
|
effort?: string;
|
|
@@ -149,6 +153,7 @@ export declare class AdvisorCardController {
|
|
|
149
153
|
private effective;
|
|
150
154
|
private pickRoute;
|
|
151
155
|
private setEffort;
|
|
156
|
+
private toggleEnabled;
|
|
152
157
|
private togglePatrol;
|
|
153
158
|
private setPatrolEverySteps;
|
|
154
159
|
private setPatrolImmuneTurns;
|
package/lib/client/controller.js
CHANGED
|
@@ -103,6 +103,7 @@ export class AdvisorCardController {
|
|
|
103
103
|
return {
|
|
104
104
|
hooks: { advisorCard: this.store },
|
|
105
105
|
pickRoute: (key) => this.pickRoute(key),
|
|
106
|
+
toggleEnabled: () => this.toggleEnabled(),
|
|
106
107
|
setEffort: (effort) => this.setEffort(effort),
|
|
107
108
|
togglePatrol: () => this.togglePatrol(),
|
|
108
109
|
setPatrolEverySteps: (steps) => this.setPatrolEverySteps(steps),
|
|
@@ -116,6 +117,7 @@ export class AdvisorCardController {
|
|
|
116
117
|
saved() {
|
|
117
118
|
const value = this.scope.getSnapshot().value;
|
|
118
119
|
return {
|
|
120
|
+
enabled: value?.enabled ?? true,
|
|
119
121
|
provider: value?.provider ?? '',
|
|
120
122
|
model: value?.model ?? '',
|
|
121
123
|
effort: value?.effort ?? '',
|
|
@@ -167,6 +169,14 @@ export class AdvisorCardController {
|
|
|
167
169
|
this.failed = false;
|
|
168
170
|
this.publish();
|
|
169
171
|
}
|
|
172
|
+
toggleEnabled() {
|
|
173
|
+
if (!this.scope.getSnapshot().writable || this.saving)
|
|
174
|
+
return;
|
|
175
|
+
const current = this.effective();
|
|
176
|
+
this.draft = { ...current, enabled: !(current.enabled ?? true) };
|
|
177
|
+
this.failed = false;
|
|
178
|
+
this.publish();
|
|
179
|
+
}
|
|
170
180
|
togglePatrol() {
|
|
171
181
|
if (!this.scope.getSnapshot().writable || this.saving)
|
|
172
182
|
return;
|
|
@@ -214,7 +224,8 @@ export class AdvisorCardController {
|
|
|
214
224
|
this.failed = false;
|
|
215
225
|
this.publish();
|
|
216
226
|
try {
|
|
217
|
-
// rc.6 scope
|
|
227
|
+
// rc.6 scope 是逐字段写:顺序写入('' / false 也写,保证旧值被清掉)
|
|
228
|
+
await this.scope.set('enabled', desired.enabled ?? true);
|
|
218
229
|
await this.scope.set('provider', desired.provider);
|
|
219
230
|
await this.scope.set('model', desired.model);
|
|
220
231
|
await this.scope.set('effort', desired.effort);
|
|
@@ -270,6 +281,7 @@ export class AdvisorCardController {
|
|
|
270
281
|
dirty: this.draft !== undefined,
|
|
271
282
|
catalogStatus: this.catalogStatus,
|
|
272
283
|
catalogPartial: this.catalogPartial,
|
|
284
|
+
enabled: effective.enabled ?? true,
|
|
273
285
|
effective,
|
|
274
286
|
patrol: {
|
|
275
287
|
enabled: effective.patrolEnabled ?? true,
|
package/lib/client/index.js
CHANGED
|
@@ -31,43 +31,74 @@ __export(index_exports, {
|
|
|
31
31
|
module.exports = __toCommonJS(index_exports);
|
|
32
32
|
|
|
33
33
|
// src/client/AdvisorCard.tsx
|
|
34
|
+
var import_react = require("react");
|
|
34
35
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
36
|
+
function ToggleSwitch(props) {
|
|
37
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
38
|
+
"button",
|
|
39
|
+
{
|
|
40
|
+
type: "button",
|
|
41
|
+
role: "switch",
|
|
42
|
+
"aria-checked": props.checked,
|
|
43
|
+
"aria-label": props.label,
|
|
44
|
+
disabled: props.disabled,
|
|
45
|
+
onClick: props.onToggle,
|
|
46
|
+
style: {
|
|
47
|
+
...style.button,
|
|
48
|
+
width: 40,
|
|
49
|
+
height: 22,
|
|
50
|
+
borderRadius: 11,
|
|
51
|
+
padding: 0,
|
|
52
|
+
position: "relative",
|
|
53
|
+
background: props.checked ? "#16a34a" : "rgba(128,128,128,.4)",
|
|
54
|
+
border: "none"
|
|
55
|
+
},
|
|
56
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: {
|
|
57
|
+
position: "absolute",
|
|
58
|
+
top: 2,
|
|
59
|
+
left: props.checked ? 20 : 2,
|
|
60
|
+
width: 18,
|
|
61
|
+
height: 18,
|
|
62
|
+
borderRadius: 9,
|
|
63
|
+
background: "#fff",
|
|
64
|
+
transition: "left .15s"
|
|
65
|
+
} })
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
function Section(props) {
|
|
70
|
+
const [open, setOpen] = (0, import_react.useState)(props.defaultOpen ?? false);
|
|
71
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: style.section, children: [
|
|
72
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
73
|
+
"button",
|
|
74
|
+
{
|
|
75
|
+
type: "button",
|
|
76
|
+
style: style.sectionHead,
|
|
77
|
+
"aria-expanded": open,
|
|
78
|
+
onClick: () => setOpen((v) => !v),
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: style.sectionTitle, children: props.title }),
|
|
81
|
+
props.subtitle === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: style.sectionSubtitle, children: props.subtitle }),
|
|
82
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: style.sectionState, children: open ? props.t("sectionCollapse") : props.t("sectionExpand") })
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
),
|
|
86
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: style.sectionBody, children: props.children }) : null
|
|
87
|
+
] });
|
|
88
|
+
}
|
|
35
89
|
function PatrolSection(props) {
|
|
36
90
|
const { t } = props;
|
|
37
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
|
|
38
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h4", { style: { ...style.title, fontSize: 13 }, children: t("patrolTitle") }),
|
|
91
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: [
|
|
39
92
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: style.description, children: t("patrolDescription") }),
|
|
40
93
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: style.effortRow, children: [
|
|
41
94
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("patrolToggle") }),
|
|
42
95
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
43
|
-
|
|
96
|
+
ToggleSwitch,
|
|
44
97
|
{
|
|
45
|
-
|
|
46
|
-
role: "switch",
|
|
47
|
-
"aria-checked": props.enabled,
|
|
48
|
-
"aria-label": t("patrolToggle"),
|
|
98
|
+
checked: props.enabled,
|
|
49
99
|
disabled: props.disabled,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
...style.button,
|
|
53
|
-
width: 40,
|
|
54
|
-
height: 22,
|
|
55
|
-
borderRadius: 11,
|
|
56
|
-
padding: 0,
|
|
57
|
-
position: "relative",
|
|
58
|
-
background: props.enabled ? "#16a34a" : "rgba(128,128,128,.4)",
|
|
59
|
-
border: "none"
|
|
60
|
-
},
|
|
61
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: {
|
|
62
|
-
position: "absolute",
|
|
63
|
-
top: 2,
|
|
64
|
-
left: props.enabled ? 20 : 2,
|
|
65
|
-
width: 18,
|
|
66
|
-
height: 18,
|
|
67
|
-
borderRadius: 9,
|
|
68
|
-
background: "#fff",
|
|
69
|
-
transition: "left .15s"
|
|
70
|
-
} })
|
|
100
|
+
label: t("patrolToggle"),
|
|
101
|
+
onToggle: props.togglePatrol
|
|
71
102
|
}
|
|
72
103
|
)
|
|
73
104
|
] }),
|
|
@@ -111,34 +142,12 @@ function PatrolSection(props) {
|
|
|
111
142
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...style.effortRow, marginTop: 8 }, children: [
|
|
112
143
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("investigateToggle") }),
|
|
113
144
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
114
|
-
|
|
145
|
+
ToggleSwitch,
|
|
115
146
|
{
|
|
116
|
-
|
|
117
|
-
role: "switch",
|
|
118
|
-
"aria-checked": props.investigate,
|
|
119
|
-
"aria-label": t("investigateToggle"),
|
|
147
|
+
checked: props.investigate,
|
|
120
148
|
disabled: props.disabled,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
...style.button,
|
|
124
|
-
width: 40,
|
|
125
|
-
height: 22,
|
|
126
|
-
borderRadius: 11,
|
|
127
|
-
padding: 0,
|
|
128
|
-
position: "relative",
|
|
129
|
-
background: props.investigate ? "#16a34a" : "rgba(128,128,128,.4)",
|
|
130
|
-
border: "none"
|
|
131
|
-
},
|
|
132
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: {
|
|
133
|
-
position: "absolute",
|
|
134
|
-
top: 2,
|
|
135
|
-
left: props.investigate ? 20 : 2,
|
|
136
|
-
width: 18,
|
|
137
|
-
height: 18,
|
|
138
|
-
borderRadius: 9,
|
|
139
|
-
background: "#fff",
|
|
140
|
-
transition: "left .15s"
|
|
141
|
-
} })
|
|
149
|
+
label: t("investigateToggle"),
|
|
150
|
+
onToggle: props.toggleInvestigate
|
|
142
151
|
}
|
|
143
152
|
)
|
|
144
153
|
] }),
|
|
@@ -166,70 +175,103 @@ var style = {
|
|
|
166
175
|
primary: { background: "#2563eb", color: "#fff", border: "none" },
|
|
167
176
|
secondary: { background: "transparent", border: "1px solid rgba(128,128,128,.5)" },
|
|
168
177
|
status: { fontSize: 12 },
|
|
169
|
-
list: { maxHeight: 260, overflowY: "auto", border: "1px solid rgba(128,128,128,.25)", borderRadius: 6, padding: "4px 10px" }
|
|
178
|
+
list: { maxHeight: 260, overflowY: "auto", border: "1px solid rgba(128,128,128,.25)", borderRadius: 6, padding: "4px 10px" },
|
|
179
|
+
switchRow: { display: "flex", alignItems: "center", gap: 8, fontSize: 13, marginTop: 2 },
|
|
180
|
+
switchLabel: { fontWeight: 600 },
|
|
181
|
+
section: { border: "1px solid rgba(128,128,128,.25)", borderRadius: 8, overflow: "hidden" },
|
|
182
|
+
sectionHead: { display: "flex", alignItems: "center", gap: 8, width: "100%", padding: "8px 10px", background: "rgba(128,128,128,.06)", border: "none", font: "inherit", textAlign: "left", cursor: "pointer" },
|
|
183
|
+
sectionTitle: { fontSize: 13, fontWeight: 600 },
|
|
184
|
+
sectionSubtitle: { fontSize: 11, opacity: 0.65, flex: 1 },
|
|
185
|
+
sectionState: { fontSize: 11, opacity: 0.65, marginLeft: "auto" },
|
|
186
|
+
sectionBody: { padding: 10, display: "flex", flexDirection: "column", gap: 8 }
|
|
170
187
|
};
|
|
171
188
|
function AdvisorCard(props) {
|
|
172
189
|
const state = props.useAdvisorCard((s) => s);
|
|
190
|
+
const enabled = state.enabled;
|
|
173
191
|
const effortOptions = state.efforts.known ? ["", ...state.efforts.levels] : EFFORT_OPTIONS;
|
|
174
192
|
const disabled = !state.writable || state.saving;
|
|
175
|
-
const armed = state.effective.provider !== "" && state.effective.model !== "";
|
|
193
|
+
const armed = enabled && state.effective.provider !== "" && state.effective.model !== "";
|
|
176
194
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: style.card, children: [
|
|
177
195
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { style: style.title, children: props.t("title") }),
|
|
178
196
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: style.description, children: props.t("description") }),
|
|
197
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: style.switchRow, children: [
|
|
198
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: style.switchLabel, children: props.t("enabledToggle") }),
|
|
199
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
200
|
+
ToggleSwitch,
|
|
201
|
+
{
|
|
202
|
+
checked: enabled,
|
|
203
|
+
disabled,
|
|
204
|
+
label: props.t("enabledToggle"),
|
|
205
|
+
onToggle: props.toggleEnabled
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
] }),
|
|
209
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: style.notice, children: enabled ? props.t("enabledHintOn") : props.t("enabledHintOff") }),
|
|
179
210
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: style.current, children: [
|
|
180
211
|
props.t("armed"),
|
|
181
212
|
"\uFF1A",
|
|
182
213
|
armed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: `${state.effective.provider}/${state.effective.model}${state.effective.effort === "" ? "" : ` (${state.effective.effort})`}` }) : props.t("offCurrent")
|
|
183
214
|
] }),
|
|
184
215
|
!state.writable ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: style.notice, children: props.t("readonly") }) : null,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
216
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
217
|
+
Section,
|
|
218
|
+
{
|
|
219
|
+
t: props.t,
|
|
220
|
+
title: props.t("modelSection"),
|
|
221
|
+
subtitle: props.t("modelSectionHint"),
|
|
222
|
+
defaultOpen: true,
|
|
223
|
+
children: [
|
|
224
|
+
state.catalogStatus === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: style.notice, children: props.t("loading") }) : null,
|
|
225
|
+
state.catalogStatus === "error" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: style.error, children: [
|
|
226
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: props.t("loadFailed") }),
|
|
227
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", style: { ...style.button, ...style.secondary }, disabled, onClick: props.retryCatalog, children: props.t("retry") })
|
|
228
|
+
] }) : null,
|
|
229
|
+
state.catalogPartial ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: style.notice, children: props.t("partial") }) : null,
|
|
230
|
+
state.groups.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
231
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: style.notice, children: props.t("choose") }),
|
|
232
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: style.list, children: state.groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
233
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: style.groupTitle, children: group.name }),
|
|
234
|
+
group.candidates.map((candidate) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: style.row, children: [
|
|
235
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
236
|
+
"input",
|
|
237
|
+
{
|
|
238
|
+
type: "radio",
|
|
239
|
+
name: "advisor-route",
|
|
240
|
+
checked: candidate.selected,
|
|
241
|
+
disabled,
|
|
242
|
+
onChange: () => {
|
|
243
|
+
props.pickRoute(candidate.key);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
),
|
|
247
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: style.modelName, children: [
|
|
248
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: candidate.modelName }),
|
|
249
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: style.route, children: `${candidate.provider}/${candidate.model}` })
|
|
250
|
+
] }),
|
|
251
|
+
!candidate.available ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: style.badge, children: props.t("unavailable") }) : null
|
|
252
|
+
] }, candidate.key))
|
|
253
|
+
] }, group.id)) })
|
|
254
|
+
] }) : state.catalogStatus === "ready" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: style.notice, children: props.t("empty") }) : null,
|
|
255
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: style.effortRow, children: [
|
|
256
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "advisor-effort", children: props.t("effort") }),
|
|
257
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
258
|
+
"select",
|
|
259
|
+
{
|
|
260
|
+
id: "advisor-effort",
|
|
261
|
+
value: state.effective.effort,
|
|
262
|
+
disabled,
|
|
263
|
+
onChange: (event) => {
|
|
264
|
+
props.setEffort(event.target.value);
|
|
265
|
+
},
|
|
266
|
+
children: effortOptions.map((option) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: option, children: option === "" ? props.t("effortDefault") : option }, option))
|
|
205
267
|
}
|
|
206
|
-
|
|
207
|
-
),
|
|
208
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: style.modelName, children: [
|
|
209
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: candidate.modelName }),
|
|
210
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: style.route, children: `${candidate.provider}/${candidate.model}` })
|
|
268
|
+
)
|
|
211
269
|
] }),
|
|
212
|
-
|
|
213
|
-
]
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
217
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "advisor-effort", children: props.t("effort") }),
|
|
218
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
219
|
-
"select",
|
|
220
|
-
{
|
|
221
|
-
id: "advisor-effort",
|
|
222
|
-
value: state.effective.effort,
|
|
223
|
-
disabled,
|
|
224
|
-
onChange: (event) => {
|
|
225
|
-
props.setEffort(event.target.value);
|
|
226
|
-
},
|
|
227
|
-
children: effortOptions.map((option) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: option, children: option === "" ? props.t("effortDefault") : option }, option))
|
|
228
|
-
}
|
|
229
|
-
)
|
|
230
|
-
] }),
|
|
231
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { ...style.notice, marginTop: -4 }, children: state.efforts.known ? props.t("effortHintKnown") : props.t("effortHint") }),
|
|
232
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
270
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { ...style.notice, marginTop: -4 }, children: state.efforts.known ? props.t("effortHintKnown") : props.t("effortHint") })
|
|
271
|
+
]
|
|
272
|
+
}
|
|
273
|
+
),
|
|
274
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, { t: props.t, title: props.t("patrolTitle"), defaultOpen: false, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
233
275
|
PatrolSection,
|
|
234
276
|
{
|
|
235
277
|
...props,
|
|
@@ -239,7 +281,7 @@ function AdvisorCard(props) {
|
|
|
239
281
|
investigate: state.patrol.investigate,
|
|
240
282
|
disabled
|
|
241
283
|
}
|
|
242
|
-
),
|
|
284
|
+
) }),
|
|
243
285
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: style.actions, children: [
|
|
244
286
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
245
287
|
"button",
|
|
@@ -268,7 +310,7 @@ function AdvisorCard(props) {
|
|
|
268
310
|
}
|
|
269
311
|
|
|
270
312
|
// src/client/AdvisorToolRow.tsx
|
|
271
|
-
var
|
|
313
|
+
var import_react2 = require("react");
|
|
272
314
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
273
315
|
var style2 = {
|
|
274
316
|
row: { display: "flex", flexDirection: "column", gap: 4, margin: "6px 0", border: "1px solid rgba(37,99,235,.35)", borderRadius: 8, overflow: "hidden" },
|
|
@@ -296,7 +338,7 @@ function blockOutput(block) {
|
|
|
296
338
|
}
|
|
297
339
|
function AdvisorToolRow(props) {
|
|
298
340
|
const t = props.t ?? ((key) => key);
|
|
299
|
-
const [expanded, setExpanded] = (0,
|
|
341
|
+
const [expanded, setExpanded] = (0, import_react2.useState)(false);
|
|
300
342
|
const block = props.block ?? {};
|
|
301
343
|
const done = block.kind !== void 0;
|
|
302
344
|
const { text, isError } = done ? blockOutput(block) : { text: "", isError: false };
|
|
@@ -318,7 +360,7 @@ function AdvisorToolRow(props) {
|
|
|
318
360
|
}
|
|
319
361
|
|
|
320
362
|
// src/client/PatrolNodeView.tsx
|
|
321
|
-
var
|
|
363
|
+
var import_react3 = require("react");
|
|
322
364
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
323
365
|
var ACCENT_ON_TRACK = {
|
|
324
366
|
border: "rgba(107,114,128,.35)",
|
|
@@ -348,7 +390,7 @@ var style3 = {
|
|
|
348
390
|
};
|
|
349
391
|
function PatrolNodeView(props) {
|
|
350
392
|
const t = props.t ?? ((key) => key);
|
|
351
|
-
const [expanded, setExpanded] = (0,
|
|
393
|
+
const [expanded, setExpanded] = (0, import_react3.useState)(false);
|
|
352
394
|
const data = props.node?.data;
|
|
353
395
|
if (data === void 0) return null;
|
|
354
396
|
const onTrack = data.verdict === "on-track";
|
|
@@ -473,6 +515,7 @@ var AdvisorCardController = class {
|
|
|
473
515
|
return {
|
|
474
516
|
hooks: { advisorCard: this.store },
|
|
475
517
|
pickRoute: (key) => this.pickRoute(key),
|
|
518
|
+
toggleEnabled: () => this.toggleEnabled(),
|
|
476
519
|
setEffort: (effort) => this.setEffort(effort),
|
|
477
520
|
togglePatrol: () => this.togglePatrol(),
|
|
478
521
|
setPatrolEverySteps: (steps) => this.setPatrolEverySteps(steps),
|
|
@@ -490,6 +533,7 @@ var AdvisorCardController = class {
|
|
|
490
533
|
saved() {
|
|
491
534
|
const value = this.scope.getSnapshot().value;
|
|
492
535
|
return {
|
|
536
|
+
enabled: value?.enabled ?? true,
|
|
493
537
|
provider: value?.provider ?? "",
|
|
494
538
|
model: value?.model ?? "",
|
|
495
539
|
effort: value?.effort ?? "",
|
|
@@ -534,6 +578,13 @@ var AdvisorCardController = class {
|
|
|
534
578
|
this.failed = false;
|
|
535
579
|
this.publish();
|
|
536
580
|
}
|
|
581
|
+
toggleEnabled() {
|
|
582
|
+
if (!this.scope.getSnapshot().writable || this.saving) return;
|
|
583
|
+
const current = this.effective();
|
|
584
|
+
this.draft = { ...current, enabled: !(current.enabled ?? true) };
|
|
585
|
+
this.failed = false;
|
|
586
|
+
this.publish();
|
|
587
|
+
}
|
|
537
588
|
togglePatrol() {
|
|
538
589
|
if (!this.scope.getSnapshot().writable || this.saving) return;
|
|
539
590
|
const current = this.effective();
|
|
@@ -575,6 +626,7 @@ var AdvisorCardController = class {
|
|
|
575
626
|
this.failed = false;
|
|
576
627
|
this.publish();
|
|
577
628
|
try {
|
|
629
|
+
await this.scope.set("enabled", desired.enabled ?? true);
|
|
578
630
|
await this.scope.set("provider", desired.provider);
|
|
579
631
|
await this.scope.set("model", desired.model);
|
|
580
632
|
await this.scope.set("effort", desired.effort);
|
|
@@ -624,6 +676,7 @@ var AdvisorCardController = class {
|
|
|
624
676
|
dirty: this.draft !== void 0,
|
|
625
677
|
catalogStatus: this.catalogStatus,
|
|
626
678
|
catalogPartial: this.catalogPartial,
|
|
679
|
+
enabled: effective.enabled ?? true,
|
|
627
680
|
effective,
|
|
628
681
|
patrol: {
|
|
629
682
|
enabled: effective.patrolEnabled ?? true,
|
|
@@ -709,6 +762,13 @@ var zh = {
|
|
|
709
762
|
saved: "\u5DF2\u4FDD\u5B58",
|
|
710
763
|
saveFailed: "\u4FDD\u5B58\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5",
|
|
711
764
|
readonly: "\u8BBE\u7F6E\u6587\u6863\u53EA\u8BFB\uFF0C\u65E0\u6CD5\u5728\u6B64\u4FEE\u6539\u3002",
|
|
765
|
+
enabledToggle: "\u542F\u7528 Advisor \u63D2\u4EF6",
|
|
766
|
+
enabledHintOn: "\u603B\u5F00\u5173\u5DF2\u6253\u5F00\uFF1A\u914D\u7F6E\u5BA1\u67E5\u6A21\u578B\u540E\uFF0Cadvisor \u5DE5\u5177\u4F1A\u5BF9\u6267\u884C\u6A21\u578B\u53EF\u89C1\u3002",
|
|
767
|
+
enabledHintOff: "\u603B\u5F00\u5173\u5DF2\u5173\u95ED\uFF1Aadvisor \u5DE5\u5177\u4E0E\u5347\u7EA7\u5B88\u5219\u4E0D\u4F1A\u6CE8\u518C\uFF0C\u5DF2\u4FDD\u5B58\u7684\u6A21\u578B\u914D\u7F6E\u4F1A\u4FDD\u7559\u3002",
|
|
768
|
+
modelSection: "\u6A21\u578B\u4E0E\u63A8\u7406\u6863\u4F4D",
|
|
769
|
+
modelSectionHint: "\u9009\u62E9\u5BA1\u67E5\u6A21\u578B\u548C\u63A8\u7406\u6863\u4F4D",
|
|
770
|
+
sectionExpand: "\u5C55\u5F00",
|
|
771
|
+
sectionCollapse: "\u6536\u8D77",
|
|
712
772
|
patrolTitle: "\u5DE1\u903B\u6A21\u5F0F\uFF08\u81EA\u52A8\u68C0\u6D4B\u8DD1\u504F\uFF09",
|
|
713
773
|
patrolDescription: "\u6267\u884C\u8FC7\u7A0B\u4E2D\u6BCF\u9694\u82E5\u5E72\u6B65\u81EA\u52A8\u628A\u4F1A\u8BDD\u5FEB\u7167\u53D1\u7ED9\u5BA1\u67E5\u6A21\u578B\u68C0\u67E5\u65B9\u5411\uFF1B\u53D1\u73B0\u8DD1\u504F\u4F1A\u5728\u4E0B\u4E00\u6B65\u6CE8\u5165\u7EA0\u504F\uFF0CSTOP \u65F6\u8981\u6C42\u6267\u884C\u6A21\u578B\u505C\u4E0B\u5411\u7528\u6237\u62A5\u544A\u3002\u6BCF\u6B21\u5DE1\u903B\u6574\u6BB5\u4F1A\u8BDD\u8BA1\u8D39\u4E00\u6B21\u5BA1\u67E5\u6A21\u578B\u3002",
|
|
714
774
|
patrolToggle: "\u542F\u7528\u5DE1\u903B",
|
|
@@ -758,6 +818,13 @@ var en = {
|
|
|
758
818
|
saved: "Saved",
|
|
759
819
|
saveFailed: "Save failed, please retry",
|
|
760
820
|
readonly: "The settings document is read-only here.",
|
|
821
|
+
enabledToggle: "Enable advisor plugin",
|
|
822
|
+
enabledHintOn: "Master switch is on: once a reviewer is configured, the advisor tool becomes visible to the executor.",
|
|
823
|
+
enabledHintOff: "Master switch is off: the advisor tool and guideline prompt section are not registered; saved settings are kept.",
|
|
824
|
+
modelSection: "Reviewer model & effort",
|
|
825
|
+
modelSectionHint: "Pick the reviewer model and reasoning effort",
|
|
826
|
+
sectionExpand: "Expand",
|
|
827
|
+
sectionCollapse: "Collapse",
|
|
761
828
|
patrolTitle: "Patrol mode (automatic drift detection)",
|
|
762
829
|
patrolDescription: "Every few steps the conversation snapshot is sent to the reviewer to check direction; on drift a correction is injected into the next request, and STOP asks the executor to halt and report. Each patrol bills the full conversation to the reviewer once.",
|
|
763
830
|
patrolToggle: "Enable patrol",
|
package/lib/client/locales.d.ts
CHANGED
|
@@ -24,6 +24,13 @@ export declare const zh: {
|
|
|
24
24
|
readonly saved: "已保存";
|
|
25
25
|
readonly saveFailed: "保存失败,请重试";
|
|
26
26
|
readonly readonly: "设置文档只读,无法在此修改。";
|
|
27
|
+
readonly enabledToggle: "启用 Advisor 插件";
|
|
28
|
+
readonly enabledHintOn: "总开关已打开:配置审查模型后,advisor 工具会对执行模型可见。";
|
|
29
|
+
readonly enabledHintOff: "总开关已关闭:advisor 工具与升级守则不会注册,已保存的模型配置会保留。";
|
|
30
|
+
readonly modelSection: "模型与推理档位";
|
|
31
|
+
readonly modelSectionHint: "选择审查模型和推理档位";
|
|
32
|
+
readonly sectionExpand: "展开";
|
|
33
|
+
readonly sectionCollapse: "收起";
|
|
27
34
|
readonly patrolTitle: "巡逻模式(自动检测跑偏)";
|
|
28
35
|
readonly patrolDescription: "执行过程中每隔若干步自动把会话快照发给审查模型检查方向;发现跑偏会在下一步注入纠偏,STOP 时要求执行模型停下向用户报告。每次巡逻整段会话计费一次审查模型。";
|
|
29
36
|
readonly patrolToggle: "启用巡逻";
|
package/lib/client/locales.js
CHANGED
|
@@ -24,6 +24,13 @@ export const zh = {
|
|
|
24
24
|
saved: '已保存',
|
|
25
25
|
saveFailed: '保存失败,请重试',
|
|
26
26
|
readonly: '设置文档只读,无法在此修改。',
|
|
27
|
+
enabledToggle: '启用 Advisor 插件',
|
|
28
|
+
enabledHintOn: '总开关已打开:配置审查模型后,advisor 工具会对执行模型可见。',
|
|
29
|
+
enabledHintOff: '总开关已关闭:advisor 工具与升级守则不会注册,已保存的模型配置会保留。',
|
|
30
|
+
modelSection: '模型与推理档位',
|
|
31
|
+
modelSectionHint: '选择审查模型和推理档位',
|
|
32
|
+
sectionExpand: '展开',
|
|
33
|
+
sectionCollapse: '收起',
|
|
27
34
|
patrolTitle: '巡逻模式(自动检测跑偏)',
|
|
28
35
|
patrolDescription: '执行过程中每隔若干步自动把会话快照发给审查模型检查方向;发现跑偏会在下一步注入纠偏,STOP 时要求执行模型停下向用户报告。每次巡逻整段会话计费一次审查模型。',
|
|
29
36
|
patrolToggle: '启用巡逻',
|
|
@@ -73,6 +80,13 @@ export const en = {
|
|
|
73
80
|
saved: 'Saved',
|
|
74
81
|
saveFailed: 'Save failed, please retry',
|
|
75
82
|
readonly: 'The settings document is read-only here.',
|
|
83
|
+
enabledToggle: 'Enable advisor plugin',
|
|
84
|
+
enabledHintOn: 'Master switch is on: once a reviewer is configured, the advisor tool becomes visible to the executor.',
|
|
85
|
+
enabledHintOff: 'Master switch is off: the advisor tool and guideline prompt section are not registered; saved settings are kept.',
|
|
86
|
+
modelSection: 'Reviewer model & effort',
|
|
87
|
+
modelSectionHint: 'Pick the reviewer model and reasoning effort',
|
|
88
|
+
sectionExpand: 'Expand',
|
|
89
|
+
sectionCollapse: 'Collapse',
|
|
76
90
|
patrolTitle: 'Patrol mode (automatic drift detection)',
|
|
77
91
|
patrolDescription: 'Every few steps the conversation snapshot is sent to the reviewer to check direction; on drift a correction is injected into the next request, and STOP asks the executor to halt and report. Each patrol bills the full conversation to the reviewer once.',
|
|
78
92
|
patrolToggle: 'Enable patrol',
|
package/lib/config.d.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* 配置三层合成(rc.6 settings 服务):schema 默认值 → 插件组合层 base →
|
|
5
5
|
* 用户层(~/.dsh/settings.yaml 的 advisor: 段,设置页卡片写入的就是这一层)。
|
|
6
6
|
*
|
|
7
|
-
* `provider` + `model`
|
|
8
|
-
* nothing"
|
|
7
|
+
* `enabled` 是用户总开关;`provider` + `model` 齐备且总开关打开时 advisor
|
|
8
|
+
* 才武装(对应 rpiv 的 "off costs nothing":关闭或没有审查模型时,工具与
|
|
9
|
+
* 提示词段都不注册)。
|
|
9
10
|
*
|
|
10
11
|
* disabledForModels 条目语法(字符串数组,与设置页文案一致):
|
|
11
12
|
* "model" —— 任意 provider 跑该模型都禁用
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
*/
|
|
15
16
|
import z from '@deepseek-ai/schemastery';
|
|
16
17
|
export interface Config {
|
|
18
|
+
enabled?: boolean;
|
|
17
19
|
provider?: string;
|
|
18
20
|
model?: string;
|
|
19
21
|
effort?: string;
|
package/lib/config.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* 配置三层合成(rc.6 settings 服务):schema 默认值 → 插件组合层 base →
|
|
5
5
|
* 用户层(~/.dsh/settings.yaml 的 advisor: 段,设置页卡片写入的就是这一层)。
|
|
6
6
|
*
|
|
7
|
-
* `provider` + `model`
|
|
8
|
-
* nothing"
|
|
7
|
+
* `enabled` 是用户总开关;`provider` + `model` 齐备且总开关打开时 advisor
|
|
8
|
+
* 才武装(对应 rpiv 的 "off costs nothing":关闭或没有审查模型时,工具与
|
|
9
|
+
* 提示词段都不注册)。
|
|
9
10
|
*
|
|
10
11
|
* disabledForModels 条目语法(字符串数组,与设置页文案一致):
|
|
11
12
|
* "model" —— 任意 provider 跑该模型都禁用
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
*/
|
|
15
16
|
import z from '@deepseek-ai/schemastery';
|
|
16
17
|
export const Config = z.object({
|
|
18
|
+
enabled: z.boolean().default(true).description('advisor 总开关:关闭后不注册 advisor 工具与升级守则提示段,已保存的模型配置保留'),
|
|
17
19
|
provider: z.string().default('').description('审查模型所在的 provider 路由名(ctx.llm 适配器路由,如 example-provider)'),
|
|
18
20
|
model: z.string().default('').description('该路由下的审查模型 id(如 deepseek-v4-pro)'),
|
|
19
21
|
effort: z.string().default('').description('审查模型推理档位;留空走模型默认'),
|
|
@@ -25,6 +27,8 @@ export const Config = z.object({
|
|
|
25
27
|
investigate: z.boolean().default(true).description('审查者调查工具:允许审查模型在裁决前用只读工具(工作区内搜索/读文件)亲自核实,建议更有据'),
|
|
26
28
|
});
|
|
27
29
|
export function resolveSelection(config) {
|
|
30
|
+
if (config.enabled === false)
|
|
31
|
+
return undefined;
|
|
28
32
|
const { provider, model } = config;
|
|
29
33
|
if (provider === undefined || provider === '' || model === undefined || model === '')
|
|
30
34
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-advisor-plugin",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "DeepSeek Harness advisor plugin: pair a fast executor with a stronger reviewer model for plan / correction / stop guidance.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"LICENSE"
|
|
40
40
|
],
|
|
41
41
|
"engines": {
|
|
42
|
-
"node": ">=
|
|
42
|
+
"node": "^22.19 || >=24"
|
|
43
43
|
},
|
|
44
44
|
"publishConfig": {
|
|
45
45
|
"registry": "https://registry.npmjs.org",
|
|
@@ -63,15 +63,15 @@
|
|
|
63
63
|
"coding-agent"
|
|
64
64
|
],
|
|
65
65
|
"peerDependencies": {
|
|
66
|
-
"@deepseek-ai/cordis": "
|
|
67
|
-
"@deepseek-ai/dsh-agent": "
|
|
68
|
-
"@deepseek-ai/dsh-commands": "
|
|
69
|
-
"@deepseek-ai/dsh-llm": "
|
|
70
|
-
"@deepseek-ai/dsh-session": "
|
|
71
|
-
"@deepseek-ai/dsh-settings": "
|
|
72
|
-
"@deepseek-ai/dsh-system-prompt": "
|
|
73
|
-
"@deepseek-ai/dsh-tools": "
|
|
74
|
-
"@deepseek-ai/schemastery": "
|
|
66
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
67
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-rc.1 || ^0.1.3-alpha.1",
|
|
68
|
+
"@deepseek-ai/dsh-commands": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-rc.1 || ^0.1.3-alpha.1",
|
|
69
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-rc.1 || ^0.1.3-alpha.1",
|
|
70
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-rc.1 || ^0.1.3-alpha.1",
|
|
71
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-rc.1 || ^0.1.3-alpha.1",
|
|
72
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-rc.1 || ^0.1.3-alpha.1",
|
|
73
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-rc.1 || ^0.1.3-alpha.1",
|
|
74
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
77
|
"@types/node": "^24.0.0",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AdvisorCard ——
|
|
2
|
+
* AdvisorCard —— 设置页“插件”栏里的 Advisor 卡片(浏览器半)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* 本组件纯渲染,样式内联(不依赖 css module 构建)。
|
|
4
|
+
* 交互:总开关 + 可折叠的模型/档位区块与巡逻区块;数据与动作全部由
|
|
5
|
+
* controller 通过 slot inject 面注入;本组件纯渲染,样式内联。
|
|
7
6
|
*/
|
|
8
7
|
|
|
8
|
+
import { useState, type ReactNode } from 'react'
|
|
9
9
|
import type { AdvisorCardFace, AdvisorCardState } from './controller.js'
|
|
10
10
|
import type { LocaleKey } from './locales.js'
|
|
11
11
|
|
|
@@ -15,6 +15,61 @@ export type AdvisorCardProps = Omit<AdvisorCardFace, 'hooks'> & {
|
|
|
15
15
|
t: (key: LocaleKey) => string
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/** 通用开关 */
|
|
19
|
+
function ToggleSwitch(props: {
|
|
20
|
+
checked: boolean
|
|
21
|
+
disabled?: boolean
|
|
22
|
+
label: string
|
|
23
|
+
onToggle: () => void
|
|
24
|
+
}) {
|
|
25
|
+
return (
|
|
26
|
+
<button
|
|
27
|
+
type="button"
|
|
28
|
+
role="switch"
|
|
29
|
+
aria-checked={props.checked}
|
|
30
|
+
aria-label={props.label}
|
|
31
|
+
disabled={props.disabled}
|
|
32
|
+
onClick={props.onToggle}
|
|
33
|
+
style={{
|
|
34
|
+
...style.button,
|
|
35
|
+
width: 40, height: 22, borderRadius: 11, padding: 0, position: 'relative',
|
|
36
|
+
background: props.checked ? '#16a34a' : 'rgba(128,128,128,.4)', border: 'none',
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
<span style={{
|
|
40
|
+
position: 'absolute', top: 2, left: props.checked ? 20 : 2,
|
|
41
|
+
width: 18, height: 18, borderRadius: 9, background: '#fff', transition: 'left .15s',
|
|
42
|
+
}} />
|
|
43
|
+
</button>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 可折叠区块:点击标题行展开/收起 */
|
|
48
|
+
function Section(props: {
|
|
49
|
+
t: (key: LocaleKey) => string
|
|
50
|
+
title: string
|
|
51
|
+
subtitle?: string
|
|
52
|
+
defaultOpen?: boolean
|
|
53
|
+
children: ReactNode
|
|
54
|
+
}) {
|
|
55
|
+
const [open, setOpen] = useState(props.defaultOpen ?? false)
|
|
56
|
+
return (
|
|
57
|
+
<div style={style.section}>
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
style={style.sectionHead}
|
|
61
|
+
aria-expanded={open}
|
|
62
|
+
onClick={() => setOpen(v => !v)}
|
|
63
|
+
>
|
|
64
|
+
<span style={style.sectionTitle}>{props.title}</span>
|
|
65
|
+
{props.subtitle === undefined ? null : <span style={style.sectionSubtitle}>{props.subtitle}</span>}
|
|
66
|
+
<span style={style.sectionState}>{open ? props.t('sectionCollapse') : props.t('sectionExpand')}</span>
|
|
67
|
+
</button>
|
|
68
|
+
{open ? <div style={style.sectionBody}>{props.children}</div> : null}
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
18
73
|
/** 巡逻模式区块(自动检测跑偏) */
|
|
19
74
|
function PatrolSection(props: AdvisorCardProps & {
|
|
20
75
|
enabled: boolean
|
|
@@ -25,29 +80,16 @@ function PatrolSection(props: AdvisorCardProps & {
|
|
|
25
80
|
}) {
|
|
26
81
|
const { t } = props
|
|
27
82
|
return (
|
|
28
|
-
<div style={{
|
|
29
|
-
<h4 style={{ ...style.title, fontSize: 13 }}>{t('patrolTitle')}</h4>
|
|
83
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
|
30
84
|
<p style={style.description}>{t('patrolDescription')}</p>
|
|
31
85
|
<div style={style.effortRow}>
|
|
32
86
|
<span>{t('patrolToggle')}</span>
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
role="switch"
|
|
36
|
-
aria-checked={props.enabled}
|
|
37
|
-
aria-label={t('patrolToggle')}
|
|
87
|
+
<ToggleSwitch
|
|
88
|
+
checked={props.enabled}
|
|
38
89
|
disabled={props.disabled}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
width: 40, height: 22, borderRadius: 11, padding: 0, position: 'relative',
|
|
43
|
-
background: props.enabled ? '#16a34a' : 'rgba(128,128,128,.4)', border: 'none',
|
|
44
|
-
}}
|
|
45
|
-
>
|
|
46
|
-
<span style={{
|
|
47
|
-
position: 'absolute', top: 2, left: props.enabled ? 20 : 2,
|
|
48
|
-
width: 18, height: 18, borderRadius: 9, background: '#fff', transition: 'left .15s',
|
|
49
|
-
}} />
|
|
50
|
-
</button>
|
|
90
|
+
label={t('patrolToggle')}
|
|
91
|
+
onToggle={props.togglePatrol}
|
|
92
|
+
/>
|
|
51
93
|
</div>
|
|
52
94
|
{props.enabled
|
|
53
95
|
? (
|
|
@@ -80,24 +122,12 @@ function PatrolSection(props: AdvisorCardProps & {
|
|
|
80
122
|
</div>
|
|
81
123
|
<div style={{ ...style.effortRow, marginTop: 8 }}>
|
|
82
124
|
<span>{t('investigateToggle')}</span>
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
role="switch"
|
|
86
|
-
aria-checked={props.investigate}
|
|
87
|
-
aria-label={t('investigateToggle')}
|
|
125
|
+
<ToggleSwitch
|
|
126
|
+
checked={props.investigate}
|
|
88
127
|
disabled={props.disabled}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
width: 40, height: 22, borderRadius: 11, padding: 0, position: 'relative',
|
|
93
|
-
background: props.investigate ? '#16a34a' : 'rgba(128,128,128,.4)', border: 'none',
|
|
94
|
-
}}
|
|
95
|
-
>
|
|
96
|
-
<span style={{
|
|
97
|
-
position: 'absolute', top: 2, left: props.investigate ? 20 : 2,
|
|
98
|
-
width: 18, height: 18, borderRadius: 9, background: '#fff', transition: 'left .15s',
|
|
99
|
-
}} />
|
|
100
|
-
</button>
|
|
128
|
+
label={t('investigateToggle')}
|
|
129
|
+
onToggle={props.toggleInvestigate}
|
|
130
|
+
/>
|
|
101
131
|
</div>
|
|
102
132
|
<p style={{ ...style.notice, marginTop: -2 }}>{t('investigateHint')}</p>
|
|
103
133
|
</div>
|
|
@@ -130,22 +160,42 @@ const style = {
|
|
|
130
160
|
secondary: { background: 'transparent', border: '1px solid rgba(128,128,128,.5)' } as const,
|
|
131
161
|
status: { fontSize: 12 } as const,
|
|
132
162
|
list: { maxHeight: 260, overflowY: 'auto', border: '1px solid rgba(128,128,128,.25)', borderRadius: 6, padding: '4px 10px' } as const,
|
|
163
|
+
switchRow: { display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, marginTop: 2 } as const,
|
|
164
|
+
switchLabel: { fontWeight: 600 } as const,
|
|
165
|
+
section: { border: '1px solid rgba(128,128,128,.25)', borderRadius: 8, overflow: 'hidden' } as const,
|
|
166
|
+
sectionHead: { display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '8px 10px', background: 'rgba(128,128,128,.06)', border: 'none', font: 'inherit', textAlign: 'left', cursor: 'pointer' } as const,
|
|
167
|
+
sectionTitle: { fontSize: 13, fontWeight: 600 } as const,
|
|
168
|
+
sectionSubtitle: { fontSize: 11, opacity: 0.65, flex: 1 } as const,
|
|
169
|
+
sectionState: { fontSize: 11, opacity: 0.65, marginLeft: 'auto' } as const,
|
|
170
|
+
sectionBody: { padding: 10, display: 'flex', flexDirection: 'column', gap: 8 } as const,
|
|
133
171
|
}
|
|
134
172
|
|
|
135
173
|
export function AdvisorCard(props: AdvisorCardProps) {
|
|
136
174
|
const state = props.useAdvisorCard(s => s)
|
|
175
|
+
const enabled = state.enabled
|
|
137
176
|
// 档位选项:目录声明了支持面 → 只列支持的;未声明 → 全档位(host 自动降级兜底)
|
|
138
177
|
const effortOptions: readonly string[] = state.efforts.known
|
|
139
178
|
? ['', ...state.efforts.levels]
|
|
140
179
|
: EFFORT_OPTIONS
|
|
141
180
|
const disabled = !state.writable || state.saving
|
|
142
|
-
const armed = state.effective.provider !== '' && state.effective.model !== ''
|
|
181
|
+
const armed = enabled && state.effective.provider !== '' && state.effective.model !== ''
|
|
143
182
|
|
|
144
183
|
return (
|
|
145
184
|
<div style={style.card}>
|
|
146
185
|
<h3 style={style.title}>{props.t('title')}</h3>
|
|
147
186
|
<p style={style.description}>{props.t('description')}</p>
|
|
148
187
|
|
|
188
|
+
<div style={style.switchRow}>
|
|
189
|
+
<span style={style.switchLabel}>{props.t('enabledToggle')}</span>
|
|
190
|
+
<ToggleSwitch
|
|
191
|
+
checked={enabled}
|
|
192
|
+
disabled={disabled}
|
|
193
|
+
label={props.t('enabledToggle')}
|
|
194
|
+
onToggle={props.toggleEnabled}
|
|
195
|
+
/>
|
|
196
|
+
</div>
|
|
197
|
+
<p style={style.notice}>{enabled ? props.t('enabledHintOn') : props.t('enabledHintOff')}</p>
|
|
198
|
+
|
|
149
199
|
<p style={style.current}>
|
|
150
200
|
{props.t('armed')}:
|
|
151
201
|
{armed
|
|
@@ -155,78 +205,87 @@ export function AdvisorCard(props: AdvisorCardProps) {
|
|
|
155
205
|
|
|
156
206
|
{!state.writable ? <p style={style.notice}>{props.t('readonly')}</p> : null}
|
|
157
207
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
208
|
+
<Section
|
|
209
|
+
t={props.t}
|
|
210
|
+
title={props.t('modelSection')}
|
|
211
|
+
subtitle={props.t('modelSectionHint')}
|
|
212
|
+
defaultOpen
|
|
213
|
+
>
|
|
214
|
+
{state.catalogStatus === 'loading' ? <p style={style.notice}>{props.t('loading')}</p> : null}
|
|
215
|
+
{state.catalogStatus === 'error'
|
|
216
|
+
? (
|
|
217
|
+
<div style={style.error}>
|
|
218
|
+
<span>{props.t('loadFailed')}</span>
|
|
219
|
+
<button type="button" style={{ ...style.button, ...style.secondary }} disabled={disabled} onClick={props.retryCatalog}>
|
|
220
|
+
{props.t('retry')}
|
|
221
|
+
</button>
|
|
222
|
+
</div>
|
|
223
|
+
)
|
|
224
|
+
: null}
|
|
225
|
+
{state.catalogPartial ? <p style={style.notice}>{props.t('partial')}</p> : null}
|
|
170
226
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
227
|
+
{state.groups.length > 0
|
|
228
|
+
? (
|
|
229
|
+
<div>
|
|
230
|
+
<p style={style.notice}>{props.t('choose')}</p>
|
|
231
|
+
<div style={style.list}>
|
|
232
|
+
{state.groups.map(group => (
|
|
233
|
+
<div key={group.id}>
|
|
234
|
+
<div style={style.groupTitle}>{group.name}</div>
|
|
235
|
+
{group.candidates.map(candidate => (
|
|
236
|
+
<label key={candidate.key} style={style.row}>
|
|
237
|
+
<input
|
|
238
|
+
type="radio"
|
|
239
|
+
name="advisor-route"
|
|
240
|
+
checked={candidate.selected}
|
|
241
|
+
disabled={disabled}
|
|
242
|
+
onChange={() => { props.pickRoute(candidate.key) }}
|
|
243
|
+
/>
|
|
244
|
+
<span style={style.modelName}>
|
|
245
|
+
<span>{candidate.modelName}</span>
|
|
246
|
+
<span style={style.route}>{`${candidate.provider}/${candidate.model}`}</span>
|
|
247
|
+
</span>
|
|
248
|
+
{!candidate.available ? <span style={style.badge}>{props.t('unavailable')}</span> : null}
|
|
249
|
+
</label>
|
|
250
|
+
))}
|
|
251
|
+
</div>
|
|
252
|
+
))}
|
|
253
|
+
</div>
|
|
197
254
|
</div>
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
: state.catalogStatus === 'ready' ? <p style={style.notice}>{props.t('empty')}</p> : null}
|
|
255
|
+
)
|
|
256
|
+
: state.catalogStatus === 'ready' ? <p style={style.notice}>{props.t('empty')}</p> : null}
|
|
201
257
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
258
|
+
<div style={style.effortRow}>
|
|
259
|
+
<label htmlFor="advisor-effort">{props.t('effort')}</label>
|
|
260
|
+
<select
|
|
261
|
+
id="advisor-effort"
|
|
262
|
+
value={state.effective.effort}
|
|
263
|
+
disabled={disabled}
|
|
264
|
+
onChange={(event) => { props.setEffort(event.target.value) }}
|
|
265
|
+
>
|
|
266
|
+
{/* 已声明支持面的模型只列支持的档位;未声明的列全档位(host 侧自动降级兜底) */}
|
|
267
|
+
{effortOptions.map(option => (
|
|
268
|
+
<option key={option} value={option}>
|
|
269
|
+
{option === '' ? props.t('effortDefault') : option}
|
|
270
|
+
</option>
|
|
271
|
+
))}
|
|
272
|
+
</select>
|
|
273
|
+
</div>
|
|
274
|
+
<p style={{ ...style.notice, marginTop: -4 }}>
|
|
275
|
+
{state.efforts.known ? props.t('effortHintKnown') : props.t('effortHint')}
|
|
276
|
+
</p>
|
|
277
|
+
</Section>
|
|
221
278
|
|
|
222
|
-
<
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
279
|
+
<Section t={props.t} title={props.t('patrolTitle')} defaultOpen={false}>
|
|
280
|
+
<PatrolSection
|
|
281
|
+
{...props}
|
|
282
|
+
enabled={state.patrol.enabled}
|
|
283
|
+
everySteps={state.patrol.everySteps}
|
|
284
|
+
immuneTurns={state.patrol.immuneTurns}
|
|
285
|
+
investigate={state.patrol.investigate}
|
|
286
|
+
disabled={disabled}
|
|
287
|
+
/>
|
|
288
|
+
</Section>
|
|
230
289
|
|
|
231
290
|
<div style={style.actions}>
|
|
232
291
|
<button
|
package/src/client/controller.ts
CHANGED
|
@@ -87,6 +87,8 @@ export interface AdvisorCardState {
|
|
|
87
87
|
dirty: boolean
|
|
88
88
|
catalogStatus: 'idle' | 'loading' | 'ready' | 'error'
|
|
89
89
|
catalogPartial: boolean
|
|
90
|
+
/** 总开关的生效值(draft 优先) */
|
|
91
|
+
enabled: boolean
|
|
90
92
|
/** 生效值(draft 优先,其次已保存值) */
|
|
91
93
|
effective: AdvisorRoute
|
|
92
94
|
/** 巡逻模式的生效值(同 draft 优先) */
|
|
@@ -99,6 +101,7 @@ export interface AdvisorCardState {
|
|
|
99
101
|
export interface AdvisorCardFace {
|
|
100
102
|
hooks: { advisorCard: SnapshotStore<AdvisorCardState> }
|
|
101
103
|
pickRoute: (key: string) => void
|
|
104
|
+
toggleEnabled: () => void
|
|
102
105
|
setEffort: (effort: string) => void
|
|
103
106
|
togglePatrol: () => void
|
|
104
107
|
setPatrolEverySteps: (steps: number) => void
|
|
@@ -175,6 +178,7 @@ export function currentEffortSupport(
|
|
|
175
178
|
}
|
|
176
179
|
|
|
177
180
|
export interface AdvisorSection {
|
|
181
|
+
enabled?: boolean
|
|
178
182
|
provider?: string
|
|
179
183
|
model?: string
|
|
180
184
|
effort?: string
|
|
@@ -217,6 +221,7 @@ export class AdvisorCardController {
|
|
|
217
221
|
return {
|
|
218
222
|
hooks: { advisorCard: this.store },
|
|
219
223
|
pickRoute: (key) => this.pickRoute(key),
|
|
224
|
+
toggleEnabled: () => this.toggleEnabled(),
|
|
220
225
|
setEffort: (effort) => this.setEffort(effort),
|
|
221
226
|
togglePatrol: () => this.togglePatrol(),
|
|
222
227
|
setPatrolEverySteps: (steps) => this.setPatrolEverySteps(steps),
|
|
@@ -231,6 +236,7 @@ export class AdvisorCardController {
|
|
|
231
236
|
private saved(): SectionDraft {
|
|
232
237
|
const value = this.scope.getSnapshot().value
|
|
233
238
|
return {
|
|
239
|
+
enabled: value?.enabled ?? true,
|
|
234
240
|
provider: value?.provider ?? '',
|
|
235
241
|
model: value?.model ?? '',
|
|
236
242
|
effort: value?.effort ?? '',
|
|
@@ -283,6 +289,14 @@ export class AdvisorCardController {
|
|
|
283
289
|
this.publish()
|
|
284
290
|
}
|
|
285
291
|
|
|
292
|
+
private toggleEnabled(): void {
|
|
293
|
+
if (!this.scope.getSnapshot().writable || this.saving) return
|
|
294
|
+
const current = this.effective()
|
|
295
|
+
this.draft = { ...current, enabled: !(current.enabled ?? true) }
|
|
296
|
+
this.failed = false
|
|
297
|
+
this.publish()
|
|
298
|
+
}
|
|
299
|
+
|
|
286
300
|
private togglePatrol(): void {
|
|
287
301
|
if (!this.scope.getSnapshot().writable || this.saving) return
|
|
288
302
|
const current = this.effective()
|
|
@@ -329,7 +343,8 @@ export class AdvisorCardController {
|
|
|
329
343
|
this.failed = false
|
|
330
344
|
this.publish()
|
|
331
345
|
try {
|
|
332
|
-
// rc.6 scope
|
|
346
|
+
// rc.6 scope 是逐字段写:顺序写入('' / false 也写,保证旧值被清掉)
|
|
347
|
+
await this.scope.set('enabled', desired.enabled ?? true)
|
|
333
348
|
await this.scope.set('provider', desired.provider)
|
|
334
349
|
await this.scope.set('model', desired.model)
|
|
335
350
|
await this.scope.set('effort', desired.effort)
|
|
@@ -381,6 +396,7 @@ export class AdvisorCardController {
|
|
|
381
396
|
dirty: this.draft !== undefined,
|
|
382
397
|
catalogStatus: this.catalogStatus,
|
|
383
398
|
catalogPartial: this.catalogPartial,
|
|
399
|
+
enabled: effective.enabled ?? true,
|
|
384
400
|
effective,
|
|
385
401
|
patrol: {
|
|
386
402
|
enabled: effective.patrolEnabled ?? true,
|
package/src/client/locales.ts
CHANGED
|
@@ -25,6 +25,13 @@ export const zh = {
|
|
|
25
25
|
saved: '已保存',
|
|
26
26
|
saveFailed: '保存失败,请重试',
|
|
27
27
|
readonly: '设置文档只读,无法在此修改。',
|
|
28
|
+
enabledToggle: '启用 Advisor 插件',
|
|
29
|
+
enabledHintOn: '总开关已打开:配置审查模型后,advisor 工具会对执行模型可见。',
|
|
30
|
+
enabledHintOff: '总开关已关闭:advisor 工具与升级守则不会注册,已保存的模型配置会保留。',
|
|
31
|
+
modelSection: '模型与推理档位',
|
|
32
|
+
modelSectionHint: '选择审查模型和推理档位',
|
|
33
|
+
sectionExpand: '展开',
|
|
34
|
+
sectionCollapse: '收起',
|
|
28
35
|
patrolTitle: '巡逻模式(自动检测跑偏)',
|
|
29
36
|
patrolDescription: '执行过程中每隔若干步自动把会话快照发给审查模型检查方向;发现跑偏会在下一步注入纠偏,STOP 时要求执行模型停下向用户报告。每次巡逻整段会话计费一次审查模型。',
|
|
30
37
|
patrolToggle: '启用巡逻',
|
|
@@ -77,6 +84,13 @@ export const en: Record<LocaleKey, string> = {
|
|
|
77
84
|
saved: 'Saved',
|
|
78
85
|
saveFailed: 'Save failed, please retry',
|
|
79
86
|
readonly: 'The settings document is read-only here.',
|
|
87
|
+
enabledToggle: 'Enable advisor plugin',
|
|
88
|
+
enabledHintOn: 'Master switch is on: once a reviewer is configured, the advisor tool becomes visible to the executor.',
|
|
89
|
+
enabledHintOff: 'Master switch is off: the advisor tool and guideline prompt section are not registered; saved settings are kept.',
|
|
90
|
+
modelSection: 'Reviewer model & effort',
|
|
91
|
+
modelSectionHint: 'Pick the reviewer model and reasoning effort',
|
|
92
|
+
sectionExpand: 'Expand',
|
|
93
|
+
sectionCollapse: 'Collapse',
|
|
80
94
|
patrolTitle: 'Patrol mode (automatic drift detection)',
|
|
81
95
|
patrolDescription: 'Every few steps the conversation snapshot is sent to the reviewer to check direction; on drift a correction is injected into the next request, and STOP asks the executor to halt and report. Each patrol bills the full conversation to the reviewer once.',
|
|
82
96
|
patrolToggle: 'Enable patrol',
|
package/src/config.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* 配置三层合成(rc.6 settings 服务):schema 默认值 → 插件组合层 base →
|
|
5
5
|
* 用户层(~/.dsh/settings.yaml 的 advisor: 段,设置页卡片写入的就是这一层)。
|
|
6
6
|
*
|
|
7
|
-
* `provider` + `model`
|
|
8
|
-
* nothing"
|
|
7
|
+
* `enabled` 是用户总开关;`provider` + `model` 齐备且总开关打开时 advisor
|
|
8
|
+
* 才武装(对应 rpiv 的 "off costs nothing":关闭或没有审查模型时,工具与
|
|
9
|
+
* 提示词段都不注册)。
|
|
9
10
|
*
|
|
10
11
|
* disabledForModels 条目语法(字符串数组,与设置页文案一致):
|
|
11
12
|
* "model" —— 任意 provider 跑该模型都禁用
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
import z from '@deepseek-ai/schemastery'
|
|
17
18
|
|
|
18
19
|
export interface Config {
|
|
20
|
+
enabled?: boolean
|
|
19
21
|
provider?: string
|
|
20
22
|
model?: string
|
|
21
23
|
effort?: string
|
|
@@ -28,6 +30,7 @@ export interface Config {
|
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
export const Config: z<Config> = z.object({
|
|
33
|
+
enabled: z.boolean().default(true).description('advisor 总开关:关闭后不注册 advisor 工具与升级守则提示段,已保存的模型配置保留'),
|
|
31
34
|
provider: z.string().default('').description('审查模型所在的 provider 路由名(ctx.llm 适配器路由,如 example-provider)'),
|
|
32
35
|
model: z.string().default('').description('该路由下的审查模型 id(如 deepseek-v4-pro)'),
|
|
33
36
|
effort: z.string().default('').description('审查模型推理档位;留空走模型默认'),
|
|
@@ -47,6 +50,7 @@ export interface Selection {
|
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
export function resolveSelection(config: Config): Selection | undefined {
|
|
53
|
+
if (config.enabled === false) return undefined
|
|
50
54
|
const { provider, model } = config
|
|
51
55
|
if (provider === undefined || provider === '' || model === undefined || model === '') return undefined
|
|
52
56
|
return config.effort === undefined || config.effort === ''
|