dsh-advisor-plugin 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +210 -0
- package/cordis.patch.yml +6 -0
- package/lib/advisor-prompt.d.ts +28 -0
- package/lib/advisor-prompt.js +91 -0
- package/lib/client/AdvisorCard.d.ts +15 -0
- package/lib/client/AdvisorCard.js +60 -0
- package/lib/client/AdvisorToolRow.d.ts +40 -0
- package/lib/client/AdvisorToolRow.js +52 -0
- package/lib/client/PatrolNodeView.d.ts +18 -0
- package/lib/client/PatrolNodeView.js +57 -0
- package/lib/client/controller.d.ts +161 -0
- package/lib/client/controller.js +287 -0
- package/lib/client/index.d.ts +20 -0
- package/lib/client/index.js +841 -0
- package/lib/client/locales.d.ts +54 -0
- package/lib/client/locales.js +101 -0
- package/lib/client/patrol-chat.d.ts +78 -0
- package/lib/client/patrol-chat.js +54 -0
- package/lib/client/store.d.ts +11 -0
- package/lib/client/store.js +23 -0
- package/lib/command.d.ts +7 -0
- package/lib/command.js +52 -0
- package/lib/config.d.ts +37 -0
- package/lib/config.js +83 -0
- package/lib/emission-guard.d.ts +32 -0
- package/lib/emission-guard.js +74 -0
- package/lib/gating.d.ts +8 -0
- package/lib/gating.js +48 -0
- package/lib/history.d.ts +20 -0
- package/lib/history.js +95 -0
- package/lib/index.d.ts +25 -0
- package/lib/index.js +96 -0
- package/lib/llm-call.d.ts +69 -0
- package/lib/llm-call.js +483 -0
- package/lib/patrol-event.d.ts +88 -0
- package/lib/patrol-event.js +68 -0
- package/lib/patrol.d.ts +51 -0
- package/lib/patrol.js +202 -0
- package/lib/prompt-section.d.ts +7 -0
- package/lib/prompt-section.js +16 -0
- package/lib/settings.d.ts +18 -0
- package/lib/settings.js +50 -0
- package/lib/tool.d.ts +22 -0
- package/lib/tool.js +91 -0
- package/package.json +91 -0
- package/src/advisor-prompt.ts +102 -0
- package/src/client/AdvisorCard.tsx +253 -0
- package/src/client/AdvisorToolRow.tsx +83 -0
- package/src/client/PatrolNodeView.tsx +85 -0
- package/src/client/controller.ts +399 -0
- package/src/client/index.ts +100 -0
- package/src/client/locales.ts +105 -0
- package/src/client/patrol-chat.ts +109 -0
- package/src/client/store.ts +29 -0
- package/src/command.ts +61 -0
- package/src/config.ts +113 -0
- package/src/emission-guard.ts +76 -0
- package/src/gating.ts +54 -0
- package/src/history.ts +102 -0
- package/src/index.ts +106 -0
- package/src/llm-call.ts +552 -0
- package/src/patrol-event.ts +115 -0
- package/src/patrol.ts +229 -0
- package/src/prompt-section.ts +21 -0
- package/src/settings.ts +74 -0
- package/src/tool.ts +114 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* locales —— Advisor 设置卡片的 zh/en 文案。
|
|
3
|
+
*/
|
|
4
|
+
export declare const zh: {
|
|
5
|
+
readonly title: "Advisor 审查模型";
|
|
6
|
+
readonly description: "执行模型卡住/做重大决策/宣告完成前,可调用零参数的 advisor() 工具升级咨询该审查模型,拿回计划、纠正或停止信号。整段会话每次调用都会转发给它——升级不免费。";
|
|
7
|
+
readonly armed: "当前审查模型";
|
|
8
|
+
readonly off: "未选择——advisor 工具对模型隐藏(零提示词成本)";
|
|
9
|
+
readonly offCurrent: "未选择";
|
|
10
|
+
readonly choose: "选择一个 DSH 已配置的模型:";
|
|
11
|
+
readonly loading: "正在加载模型目录…";
|
|
12
|
+
readonly loadFailed: "模型目录加载失败";
|
|
13
|
+
readonly retry: "重试";
|
|
14
|
+
readonly partial: "部分 provider 的模型列表获取失败,列表可能不完整。";
|
|
15
|
+
readonly empty: "没有可配置的模型——请先在 设置 → 模型 里配置 provider。";
|
|
16
|
+
readonly effort: "推理档位";
|
|
17
|
+
readonly effortDefault: "(模型默认)";
|
|
18
|
+
readonly effortHint: "留空走模型默认;该模型未声明支持档位——若配置的档位不被支持,插件会自动降级为默认档(日志可见)。";
|
|
19
|
+
readonly effortHintKnown: "只列出该模型声明支持的档位;留空走模型默认。";
|
|
20
|
+
readonly unavailable: "(已配置的路由不在当前模型目录中)";
|
|
21
|
+
readonly save: "保存";
|
|
22
|
+
readonly saving: "保存中…";
|
|
23
|
+
readonly discard: "放弃更改";
|
|
24
|
+
readonly saved: "已保存";
|
|
25
|
+
readonly saveFailed: "保存失败,请重试";
|
|
26
|
+
readonly readonly: "设置文档只读,无法在此修改。";
|
|
27
|
+
readonly patrolTitle: "巡逻模式(自动检测跑偏)";
|
|
28
|
+
readonly patrolDescription: "执行过程中每隔若干步自动把会话快照发给审查模型检查方向;发现跑偏会在下一步注入纠偏,STOP 时要求执行模型停下向用户报告。每次巡逻整段会话计费一次审查模型。";
|
|
29
|
+
readonly patrolToggle: "启用巡逻";
|
|
30
|
+
readonly patrolEverySteps: "巡逻间隔(步数)";
|
|
31
|
+
readonly patrolEveryStepsHint: "每 N 个模型请求做一次巡逻检查;两次巡逻之间另有至少 90 秒的节流。";
|
|
32
|
+
readonly patrolImmuneTurns: "纠偏冷却(请求数)";
|
|
33
|
+
readonly investigateToggle: "审查者调查工具";
|
|
34
|
+
readonly investigateHint: "允许审查模型在裁决前用只读工具(工作区内搜索/读文件)亲自核实事实;重复建议与空话建议会被自动去重/丢弃。";
|
|
35
|
+
readonly toolTitle: "咨询";
|
|
36
|
+
readonly toolRunning: "进行中";
|
|
37
|
+
readonly toolDone: "完成";
|
|
38
|
+
readonly toolFailed: "失败";
|
|
39
|
+
readonly toolConsulting: "正在向审查模型转发整段会话…";
|
|
40
|
+
readonly toolExpand: "展开建议全文";
|
|
41
|
+
readonly toolCollapse: "收起";
|
|
42
|
+
readonly patrolCardTitle: "巡逻裁决";
|
|
43
|
+
readonly patrolCheckTitle: "巡逻检查";
|
|
44
|
+
readonly patrolVerdictOnTrack: "正常";
|
|
45
|
+
readonly patrolVerdictCorrection: "纠偏";
|
|
46
|
+
readonly patrolVerdictStop: "叫停";
|
|
47
|
+
readonly patrolCardStepPrefix: "第 ";
|
|
48
|
+
readonly patrolCardStepSuffix: " 步";
|
|
49
|
+
readonly patrolExpand: "展开裁决全文";
|
|
50
|
+
readonly patrolCollapse: "收起";
|
|
51
|
+
readonly patrolDowngradedTag: "冷却降级·未注入";
|
|
52
|
+
};
|
|
53
|
+
export type LocaleKey = keyof typeof zh;
|
|
54
|
+
export declare const en: Record<LocaleKey, string>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* locales —— Advisor 设置卡片的 zh/en 文案。
|
|
3
|
+
*/
|
|
4
|
+
export const zh = {
|
|
5
|
+
title: 'Advisor 审查模型',
|
|
6
|
+
description: '执行模型卡住/做重大决策/宣告完成前,可调用零参数的 advisor() 工具升级咨询该审查模型,拿回计划、纠正或停止信号。整段会话每次调用都会转发给它——升级不免费。',
|
|
7
|
+
armed: '当前审查模型',
|
|
8
|
+
off: '未选择——advisor 工具对模型隐藏(零提示词成本)',
|
|
9
|
+
offCurrent: '未选择',
|
|
10
|
+
choose: '选择一个 DSH 已配置的模型:',
|
|
11
|
+
loading: '正在加载模型目录…',
|
|
12
|
+
loadFailed: '模型目录加载失败',
|
|
13
|
+
retry: '重试',
|
|
14
|
+
partial: '部分 provider 的模型列表获取失败,列表可能不完整。',
|
|
15
|
+
empty: '没有可配置的模型——请先在 设置 → 模型 里配置 provider。',
|
|
16
|
+
effort: '推理档位',
|
|
17
|
+
effortDefault: '(模型默认)',
|
|
18
|
+
effortHint: '留空走模型默认;该模型未声明支持档位——若配置的档位不被支持,插件会自动降级为默认档(日志可见)。',
|
|
19
|
+
effortHintKnown: '只列出该模型声明支持的档位;留空走模型默认。',
|
|
20
|
+
unavailable: '(已配置的路由不在当前模型目录中)',
|
|
21
|
+
save: '保存',
|
|
22
|
+
saving: '保存中…',
|
|
23
|
+
discard: '放弃更改',
|
|
24
|
+
saved: '已保存',
|
|
25
|
+
saveFailed: '保存失败,请重试',
|
|
26
|
+
readonly: '设置文档只读,无法在此修改。',
|
|
27
|
+
patrolTitle: '巡逻模式(自动检测跑偏)',
|
|
28
|
+
patrolDescription: '执行过程中每隔若干步自动把会话快照发给审查模型检查方向;发现跑偏会在下一步注入纠偏,STOP 时要求执行模型停下向用户报告。每次巡逻整段会话计费一次审查模型。',
|
|
29
|
+
patrolToggle: '启用巡逻',
|
|
30
|
+
patrolEverySteps: '巡逻间隔(步数)',
|
|
31
|
+
patrolEveryStepsHint: '每 N 个模型请求做一次巡逻检查;两次巡逻之间另有至少 90 秒的节流。',
|
|
32
|
+
patrolImmuneTurns: '纠偏冷却(请求数)',
|
|
33
|
+
investigateToggle: '审查者调查工具',
|
|
34
|
+
investigateHint: '允许审查模型在裁决前用只读工具(工作区内搜索/读文件)亲自核实事实;重复建议与空话建议会被自动去重/丢弃。',
|
|
35
|
+
toolTitle: '咨询',
|
|
36
|
+
toolRunning: '进行中',
|
|
37
|
+
toolDone: '完成',
|
|
38
|
+
toolFailed: '失败',
|
|
39
|
+
toolConsulting: '正在向审查模型转发整段会话…',
|
|
40
|
+
toolExpand: '展开建议全文',
|
|
41
|
+
toolCollapse: '收起',
|
|
42
|
+
patrolCardTitle: '巡逻裁决',
|
|
43
|
+
patrolCheckTitle: '巡逻检查',
|
|
44
|
+
patrolVerdictOnTrack: '正常',
|
|
45
|
+
patrolVerdictCorrection: '纠偏',
|
|
46
|
+
patrolVerdictStop: '叫停',
|
|
47
|
+
patrolCardStepPrefix: '第 ',
|
|
48
|
+
patrolCardStepSuffix: ' 步',
|
|
49
|
+
patrolExpand: '展开裁决全文',
|
|
50
|
+
patrolCollapse: '收起',
|
|
51
|
+
patrolDowngradedTag: '冷却降级·未注入',
|
|
52
|
+
};
|
|
53
|
+
export const en = {
|
|
54
|
+
title: 'Advisor reviewer model',
|
|
55
|
+
description: 'The executor model may call the zero-parameter advisor() tool when stuck, before major decisions, or before declaring done. The whole conversation is forwarded to this reviewer each call — escalation is not free.',
|
|
56
|
+
armed: 'Current reviewer',
|
|
57
|
+
off: 'None selected — the advisor tool is hidden from the model (zero prompt cost)',
|
|
58
|
+
offCurrent: 'None selected',
|
|
59
|
+
choose: 'Pick one of the models DSH already has configured:',
|
|
60
|
+
loading: 'Loading model catalog…',
|
|
61
|
+
loadFailed: 'Failed to load the model catalog',
|
|
62
|
+
retry: 'Retry',
|
|
63
|
+
partial: 'Some providers failed to list models; the catalog may be incomplete.',
|
|
64
|
+
empty: 'No configurable models — configure a provider under Settings → Models first.',
|
|
65
|
+
effort: 'Reasoning effort',
|
|
66
|
+
effortDefault: '(model default)',
|
|
67
|
+
effortHint: 'Leave empty for the model default; this model declares no effort support — an unsupported configured effort falls back to the model default automatically (see logs).',
|
|
68
|
+
effortHintKnown: 'Only levels this model declares as supported are listed; empty means the model default.',
|
|
69
|
+
unavailable: '(configured route is not in the current catalog)',
|
|
70
|
+
save: 'Save',
|
|
71
|
+
saving: 'Saving…',
|
|
72
|
+
discard: 'Discard changes',
|
|
73
|
+
saved: 'Saved',
|
|
74
|
+
saveFailed: 'Save failed, please retry',
|
|
75
|
+
readonly: 'The settings document is read-only here.',
|
|
76
|
+
patrolTitle: 'Patrol mode (automatic drift detection)',
|
|
77
|
+
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
|
+
patrolToggle: 'Enable patrol',
|
|
79
|
+
patrolEverySteps: 'Patrol interval (steps)',
|
|
80
|
+
patrolEveryStepsHint: 'One patrol check per N model requests; throttled to at most one patrol per 90 seconds.',
|
|
81
|
+
patrolImmuneTurns: 'Correction cooldown (requests)',
|
|
82
|
+
investigateToggle: 'Reviewer investigation tools',
|
|
83
|
+
investigateHint: 'Let the reviewer verify facts with read-only tools (workspace search / file read) before ruling; duplicate and content-free advice is auto-deduped/dropped.',
|
|
84
|
+
toolTitle: 'consultation',
|
|
85
|
+
toolRunning: 'running',
|
|
86
|
+
toolDone: 'done',
|
|
87
|
+
toolFailed: 'failed',
|
|
88
|
+
toolConsulting: 'forwarding the whole conversation to the reviewer…',
|
|
89
|
+
toolExpand: 'show guidance',
|
|
90
|
+
toolCollapse: 'collapse',
|
|
91
|
+
patrolCardTitle: 'patrol verdict',
|
|
92
|
+
patrolCheckTitle: 'patrol check',
|
|
93
|
+
patrolVerdictOnTrack: 'on track',
|
|
94
|
+
patrolVerdictCorrection: 'correction',
|
|
95
|
+
patrolVerdictStop: 'stop',
|
|
96
|
+
patrolCardStepPrefix: 'step ',
|
|
97
|
+
patrolCardStepSuffix: '',
|
|
98
|
+
patrolExpand: 'show verdict',
|
|
99
|
+
patrolCollapse: 'collapse',
|
|
100
|
+
patrolDowngradedTag: 'cooldown·not injected',
|
|
101
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* patrol-chat —— 巡逻裁决的会话聊天视图贡献(client 半)。
|
|
3
|
+
*
|
|
4
|
+
* 机制(0.1.1 实测反编译确认):
|
|
5
|
+
* - 会话事件流 → conversationEvents 注册的 Definition(事件→业务上下文
|
|
6
|
+
* 状态机)→ buildViewNode 物化聊天节点(kind 为判别键)→ ChatNodeSeat
|
|
7
|
+
* 按节点 kind 派发 keyed `conversation.chat.node` 槽 → 我们注册的
|
|
8
|
+
* key 'advisor-patrol' 卡片渲染。
|
|
9
|
+
* - Definition 匹配不独占:所有命中者各自物化。我们的 match 只认
|
|
10
|
+
* plugin/point 双判别字段,stock 的 messageDefinition 等不会碰
|
|
11
|
+
* log-only 的 hook/invoked,fallback 也只认 surface 事件——因此
|
|
12
|
+
* 该事件在全 UI 里只有这一张卡片,无重复渲染。
|
|
13
|
+
* - 未装载本插件客户端(如 CLI 会话)时事件零渲染,静默无害。
|
|
14
|
+
*
|
|
15
|
+
* 类型策略:conversationEvents 服务在 0.1.1 声明于 dsh-client-runtime
|
|
16
|
+
* (本包未链接、版本间漂移),此处按结构收窄——同 controller.ts 对
|
|
17
|
+
* settingsScope 的做法,版本无关。
|
|
18
|
+
*/
|
|
19
|
+
import { type PatrolEventData } from '../patrol-event.js';
|
|
20
|
+
/** 会话事件的最小结构(client 侧收窄读取) */
|
|
21
|
+
export interface PatrolEventLike {
|
|
22
|
+
readonly type?: unknown;
|
|
23
|
+
readonly seq: number;
|
|
24
|
+
readonly time: number;
|
|
25
|
+
readonly data?: unknown;
|
|
26
|
+
readonly location?: unknown;
|
|
27
|
+
}
|
|
28
|
+
/** Definition match 返回的身份 */
|
|
29
|
+
interface PatrolMatchResult {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly role: 'start';
|
|
32
|
+
}
|
|
33
|
+
/** 引擎喂给 start/buildViewNode 的上下文(最小结构) */
|
|
34
|
+
interface PatrolNodeContext {
|
|
35
|
+
readonly key: string;
|
|
36
|
+
readonly id: string;
|
|
37
|
+
readonly start?: {
|
|
38
|
+
readonly location?: unknown;
|
|
39
|
+
} | undefined;
|
|
40
|
+
readonly matches?: readonly {
|
|
41
|
+
readonly location?: unknown;
|
|
42
|
+
}[] | undefined;
|
|
43
|
+
readonly state?: PatrolNodeState | undefined;
|
|
44
|
+
}
|
|
45
|
+
/** 节点数据 = 事件载荷 + 日志定位(卡片渲染的全部输入) */
|
|
46
|
+
export interface PatrolNodeState extends PatrolEventData {
|
|
47
|
+
readonly seq: number;
|
|
48
|
+
readonly time: number;
|
|
49
|
+
}
|
|
50
|
+
/** 聊天节点的最终形态(ChatConversationViewNode 的结构子集) */
|
|
51
|
+
export interface PatrolChatViewNode {
|
|
52
|
+
readonly key: string;
|
|
53
|
+
readonly kind: 'advisor-patrol';
|
|
54
|
+
readonly id: string;
|
|
55
|
+
readonly target: 'chat';
|
|
56
|
+
readonly anchorSeq: number;
|
|
57
|
+
readonly location: unknown;
|
|
58
|
+
readonly visibility: 'visible';
|
|
59
|
+
readonly data: PatrolNodeState;
|
|
60
|
+
}
|
|
61
|
+
/** 巡逻裁决的聊天 Definition(纯工厂,可单测) */
|
|
62
|
+
export declare function patrolChatDefinition(): {
|
|
63
|
+
kind: 'advisor-patrol';
|
|
64
|
+
target: 'chat';
|
|
65
|
+
match(event: PatrolEventLike): PatrolMatchResult | null;
|
|
66
|
+
start(_context: unknown, match: {
|
|
67
|
+
event: PatrolEventLike;
|
|
68
|
+
}): PatrolNodeState;
|
|
69
|
+
update(context: {
|
|
70
|
+
state: PatrolNodeState;
|
|
71
|
+
}): PatrolNodeState;
|
|
72
|
+
buildViewNode(context: PatrolNodeContext): PatrolChatViewNode | null;
|
|
73
|
+
};
|
|
74
|
+
/** conversationEvents 服务的结构化最小面(版本无关收窄) */
|
|
75
|
+
export interface ConversationEventsService {
|
|
76
|
+
register(definition: ReturnType<typeof patrolChatDefinition>): () => void;
|
|
77
|
+
}
|
|
78
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* patrol-chat —— 巡逻裁决的会话聊天视图贡献(client 半)。
|
|
3
|
+
*
|
|
4
|
+
* 机制(0.1.1 实测反编译确认):
|
|
5
|
+
* - 会话事件流 → conversationEvents 注册的 Definition(事件→业务上下文
|
|
6
|
+
* 状态机)→ buildViewNode 物化聊天节点(kind 为判别键)→ ChatNodeSeat
|
|
7
|
+
* 按节点 kind 派发 keyed `conversation.chat.node` 槽 → 我们注册的
|
|
8
|
+
* key 'advisor-patrol' 卡片渲染。
|
|
9
|
+
* - Definition 匹配不独占:所有命中者各自物化。我们的 match 只认
|
|
10
|
+
* plugin/point 双判别字段,stock 的 messageDefinition 等不会碰
|
|
11
|
+
* log-only 的 hook/invoked,fallback 也只认 surface 事件——因此
|
|
12
|
+
* 该事件在全 UI 里只有这一张卡片,无重复渲染。
|
|
13
|
+
* - 未装载本插件客户端(如 CLI 会话)时事件零渲染,静默无害。
|
|
14
|
+
*
|
|
15
|
+
* 类型策略:conversationEvents 服务在 0.1.1 声明于 dsh-client-runtime
|
|
16
|
+
* (本包未链接、版本间漂移),此处按结构收窄——同 controller.ts 对
|
|
17
|
+
* settingsScope 的做法,版本无关。
|
|
18
|
+
*/
|
|
19
|
+
import { isPatrolVerdictEvent } from '../patrol-event.js';
|
|
20
|
+
/** 事件定位兜底:start 匹配的位置 → 首个匹配的位置 → 未解析 */
|
|
21
|
+
function patrolLocation(context) {
|
|
22
|
+
return context.start?.location ?? context.matches?.[0]?.location ?? { kind: 'unresolved' };
|
|
23
|
+
}
|
|
24
|
+
/** 巡逻裁决的聊天 Definition(纯工厂,可单测) */
|
|
25
|
+
export function patrolChatDefinition() {
|
|
26
|
+
return {
|
|
27
|
+
kind: 'advisor-patrol',
|
|
28
|
+
target: 'chat',
|
|
29
|
+
match: (event) => {
|
|
30
|
+
if (!isPatrolVerdictEvent(event))
|
|
31
|
+
return null;
|
|
32
|
+
return { id: `advisor-patrol-${event.seq}`, role: 'start' };
|
|
33
|
+
},
|
|
34
|
+
start: (_context, match) => {
|
|
35
|
+
const data = match.event.data;
|
|
36
|
+
return { ...data, seq: match.event.seq, time: match.event.time };
|
|
37
|
+
},
|
|
38
|
+
update: (context) => context.state,
|
|
39
|
+
buildViewNode: (context) => {
|
|
40
|
+
if (context.state === undefined)
|
|
41
|
+
return null;
|
|
42
|
+
return {
|
|
43
|
+
key: context.key,
|
|
44
|
+
kind: 'advisor-patrol',
|
|
45
|
+
id: context.id,
|
|
46
|
+
target: 'chat',
|
|
47
|
+
anchorSeq: context.state.seq,
|
|
48
|
+
location: patrolLocation(context),
|
|
49
|
+
visibility: 'visible',
|
|
50
|
+
data: context.state,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* store —— 极简快照存储(结构兼容渲染器的 HostObservable:
|
|
3
|
+
* { getSnapshot, subscribe }),避免对官方 client-store 的值导入,
|
|
4
|
+
* 让外部 client 插件保持零运行时外部依赖。
|
|
5
|
+
*/
|
|
6
|
+
export interface SnapshotStore<T> {
|
|
7
|
+
getSnapshot(): T;
|
|
8
|
+
subscribe(fn: () => void): () => void;
|
|
9
|
+
set(value: T): void;
|
|
10
|
+
}
|
|
11
|
+
export declare function createSnapshotStore<T>(initial: T): SnapshotStore<T>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* store —— 极简快照存储(结构兼容渲染器的 HostObservable:
|
|
3
|
+
* { getSnapshot, subscribe }),避免对官方 client-store 的值导入,
|
|
4
|
+
* 让外部 client 插件保持零运行时外部依赖。
|
|
5
|
+
*/
|
|
6
|
+
export function createSnapshotStore(initial) {
|
|
7
|
+
let current = initial;
|
|
8
|
+
const listeners = new Set();
|
|
9
|
+
return {
|
|
10
|
+
getSnapshot: () => current,
|
|
11
|
+
subscribe: (fn) => {
|
|
12
|
+
listeners.add(fn);
|
|
13
|
+
return () => {
|
|
14
|
+
listeners.delete(fn);
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
set: (value) => {
|
|
18
|
+
current = value;
|
|
19
|
+
for (const fn of listeners)
|
|
20
|
+
fn();
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
package/lib/command.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* command —— `/advisor` 人类命令:报告当前审查模型选择、逐 provider 列出
|
|
3
|
+
* 可配置模型(来自 llm 服务的模型目录)、以及黑名单状态。不产生模型消息。
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
import { type Config, type Selection } from './config.js';
|
|
7
|
+
export declare function registerAdvisorCommand(ctx: Context, getConfig: () => Config, getSelection: () => Selection | undefined, getSettingsInfo: () => string): (() => void) | undefined;
|
package/lib/command.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* command —— `/advisor` 人类命令:报告当前审查模型选择、逐 provider 列出
|
|
3
|
+
* 可配置模型(来自 llm 服务的模型目录)、以及黑名单状态。不产生模型消息。
|
|
4
|
+
*/
|
|
5
|
+
import { selectionLabel } from './config.js';
|
|
6
|
+
export function registerAdvisorCommand(ctx, getConfig, getSelection, getSettingsInfo) {
|
|
7
|
+
const commands = ctx.get('commands');
|
|
8
|
+
if (commands === undefined)
|
|
9
|
+
return undefined;
|
|
10
|
+
return commands.register({
|
|
11
|
+
name: 'advisor',
|
|
12
|
+
description: '查看 advisor(审查模型)配置与各 provider 可用模型',
|
|
13
|
+
handler: async () => {
|
|
14
|
+
const selection = getSelection();
|
|
15
|
+
const current = selection === undefined
|
|
16
|
+
? '未配置审查模型——advisor 工具对模型不可见。'
|
|
17
|
+
: `Advisor:${selectionLabel(selection)}`;
|
|
18
|
+
const providers = ctx.llm.listProviders();
|
|
19
|
+
let routesBlock;
|
|
20
|
+
if (providers.length === 0) {
|
|
21
|
+
routesBlock = '没有已注册的 provider 路由(需要挂载 llm 适配器,如 llm-pi-ai)。';
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const perProvider = await Promise.all(providers.map(async (p) => {
|
|
25
|
+
try {
|
|
26
|
+
const models = await ctx.llm.listModels(p.id);
|
|
27
|
+
const list = models.length === 0 ? '(无模型)' : models.map(m => m.id).join(', ');
|
|
28
|
+
return `- ${p.id}(${p.name}): ${list}`;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return `- ${p.id}(${p.name}): 模型列表获取失败`;
|
|
32
|
+
}
|
|
33
|
+
}));
|
|
34
|
+
routesBlock = ['已注册 provider 路由与模型:', ...perProvider].join('\n');
|
|
35
|
+
}
|
|
36
|
+
const config = getConfig();
|
|
37
|
+
const blocklist = (config.disabledForModels ?? []).length === 0
|
|
38
|
+
? '执行模型黑名单:(空)'
|
|
39
|
+
: `执行模型黑名单:${config.disabledForModels?.join(', ')}`;
|
|
40
|
+
return {
|
|
41
|
+
kind: 'success',
|
|
42
|
+
text: [
|
|
43
|
+
current,
|
|
44
|
+
routesBlock,
|
|
45
|
+
blocklist,
|
|
46
|
+
getSettingsInfo(),
|
|
47
|
+
'在 设置 → 插件 → Advisor 卡片中选择审查模型,或直接编辑 ~/.dsh/settings.yaml 的 advisor: 段。',
|
|
48
|
+
].join('\n'),
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config —— schemastery 配置 schema、审查模型选择解析、执行模型黑名单语法。
|
|
3
|
+
*
|
|
4
|
+
* 配置三层合成(rc.6 settings 服务):schema 默认值 → 插件组合层 base →
|
|
5
|
+
* 用户层(~/.dsh/settings.yaml 的 advisor: 段,设置页卡片写入的就是这一层)。
|
|
6
|
+
*
|
|
7
|
+
* `provider` + `model` 两者齐备 advisor 才武装(对应 rpiv 的 "off costs
|
|
8
|
+
* nothing":没有审查模型时工具与提示词段都不注册)。
|
|
9
|
+
*
|
|
10
|
+
* disabledForModels 条目语法(字符串数组,与设置页文案一致):
|
|
11
|
+
* "model" —— 任意 provider 跑该模型都禁用
|
|
12
|
+
* "provider/model" —— 精确路由禁用
|
|
13
|
+
* "provider/model@effort" —— 档位达到 effort 及以上才禁用
|
|
14
|
+
*/
|
|
15
|
+
import z from '@deepseek-ai/schemastery';
|
|
16
|
+
export interface Config {
|
|
17
|
+
provider?: string;
|
|
18
|
+
model?: string;
|
|
19
|
+
effort?: string;
|
|
20
|
+
disabledForModels?: string[];
|
|
21
|
+
guidelines?: string[];
|
|
22
|
+
patrolEnabled?: boolean;
|
|
23
|
+
patrolEverySteps?: number;
|
|
24
|
+
patrolImmuneTurns?: number;
|
|
25
|
+
investigate?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare const Config: z<Config>;
|
|
28
|
+
/** 已武装的审查路由;undefined = advisor 关闭 */
|
|
29
|
+
export interface Selection {
|
|
30
|
+
readonly provider: string;
|
|
31
|
+
readonly model: string;
|
|
32
|
+
readonly effort?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function resolveSelection(config: Config): Selection | undefined;
|
|
35
|
+
export declare function selectionLabel(selection: Selection): string;
|
|
36
|
+
/** 执行路由在当前档位下是否命中黑名单(fail-soft:未知档位/未知阈值不禁用) */
|
|
37
|
+
export declare function isExecutorBlocked(config: Config, provider: string | undefined, model: string | undefined, reasoningEffort: string | undefined): boolean;
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config —— schemastery 配置 schema、审查模型选择解析、执行模型黑名单语法。
|
|
3
|
+
*
|
|
4
|
+
* 配置三层合成(rc.6 settings 服务):schema 默认值 → 插件组合层 base →
|
|
5
|
+
* 用户层(~/.dsh/settings.yaml 的 advisor: 段,设置页卡片写入的就是这一层)。
|
|
6
|
+
*
|
|
7
|
+
* `provider` + `model` 两者齐备 advisor 才武装(对应 rpiv 的 "off costs
|
|
8
|
+
* nothing":没有审查模型时工具与提示词段都不注册)。
|
|
9
|
+
*
|
|
10
|
+
* disabledForModels 条目语法(字符串数组,与设置页文案一致):
|
|
11
|
+
* "model" —— 任意 provider 跑该模型都禁用
|
|
12
|
+
* "provider/model" —— 精确路由禁用
|
|
13
|
+
* "provider/model@effort" —— 档位达到 effort 及以上才禁用
|
|
14
|
+
*/
|
|
15
|
+
import z from '@deepseek-ai/schemastery';
|
|
16
|
+
export const Config = z.object({
|
|
17
|
+
provider: z.string().default('').description('审查模型所在的 provider 路由名(ctx.llm 适配器路由,如 example-provider)'),
|
|
18
|
+
model: z.string().default('').description('该路由下的审查模型 id(如 deepseek-v4-pro)'),
|
|
19
|
+
effort: z.string().default('').description('审查模型推理档位;留空走模型默认'),
|
|
20
|
+
disabledForModels: z.array(z.string()).default([]).description('执行模型黑名单:"model" / "provider/model" / "provider/model@minEffort"'),
|
|
21
|
+
guidelines: z.array(z.string()).default([]).description('覆盖默认的升级守则(系统提示段文本,每条一行)'),
|
|
22
|
+
patrolEnabled: z.boolean().default(true).description('巡逻模式:执行中每 N 步自动把会话快照发给审查模型检查是否跑偏,跑偏时注入纠偏'),
|
|
23
|
+
patrolEverySteps: z.number().default(6).min(2).max(500).description('巡逻间隔(模型请求步数);每次巡逻整段会话计费一次审查模型。当保底用可以配大(如 100)'),
|
|
24
|
+
patrolImmuneTurns: z.number().default(3).min(0).max(20).description('纠偏冷却:一次纠偏注入后的 N 个请求内,新的 CORRECTION 降级为只出卡片不注入(STOP 不受冷却限制)'),
|
|
25
|
+
investigate: z.boolean().default(true).description('审查者调查工具:允许审查模型在裁决前用只读工具(工作区内搜索/读文件)亲自核实,建议更有据'),
|
|
26
|
+
});
|
|
27
|
+
export function resolveSelection(config) {
|
|
28
|
+
const { provider, model } = config;
|
|
29
|
+
if (provider === undefined || provider === '' || model === undefined || model === '')
|
|
30
|
+
return undefined;
|
|
31
|
+
return config.effort === undefined || config.effort === ''
|
|
32
|
+
? { provider, model }
|
|
33
|
+
: { provider, model, effort: config.effort };
|
|
34
|
+
}
|
|
35
|
+
export function selectionLabel(selection) {
|
|
36
|
+
return selection.effort === undefined
|
|
37
|
+
? `${selection.provider}/${selection.model}`
|
|
38
|
+
: `${selection.provider}/${selection.model} (${selection.effort})`;
|
|
39
|
+
}
|
|
40
|
+
// "@minEffort" 阈值比较用的档位序;未知档位排名 NaN 而不是猜
|
|
41
|
+
const EFFORT_LADDER = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
|
|
42
|
+
function effortRank(effort) {
|
|
43
|
+
const index = EFFORT_LADDER.indexOf(effort);
|
|
44
|
+
return index === -1 ? Number.NaN : index;
|
|
45
|
+
}
|
|
46
|
+
function parseRule(entry) {
|
|
47
|
+
const parts = entry.split('@');
|
|
48
|
+
const route = parts[0] ?? '';
|
|
49
|
+
const minEffort = parts[1];
|
|
50
|
+
const trimmed = route.trim();
|
|
51
|
+
if (trimmed === '')
|
|
52
|
+
return undefined;
|
|
53
|
+
const slash = trimmed.indexOf('/');
|
|
54
|
+
if (slash === -1)
|
|
55
|
+
return { model: trimmed, ...(minEffort === undefined ? {} : { minEffort }) };
|
|
56
|
+
return {
|
|
57
|
+
provider: trimmed.slice(0, slash),
|
|
58
|
+
model: trimmed.slice(slash + 1),
|
|
59
|
+
...(minEffort === undefined ? {} : { minEffort }),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** 执行路由在当前档位下是否命中黑名单(fail-soft:未知档位/未知阈值不禁用) */
|
|
63
|
+
export function isExecutorBlocked(config, provider, model, reasoningEffort) {
|
|
64
|
+
if (provider === undefined || model === undefined)
|
|
65
|
+
return false;
|
|
66
|
+
for (const entry of config.disabledForModels ?? []) {
|
|
67
|
+
const rule = parseRule(entry);
|
|
68
|
+
if (rule === undefined)
|
|
69
|
+
continue;
|
|
70
|
+
if (rule.provider !== undefined && rule.provider !== provider)
|
|
71
|
+
continue;
|
|
72
|
+
if (rule.model !== model)
|
|
73
|
+
continue;
|
|
74
|
+
if (rule.minEffort !== undefined) {
|
|
75
|
+
const current = effortRank(reasoningEffort ?? '');
|
|
76
|
+
const floor = effortRank(rule.minEffort);
|
|
77
|
+
if (Number.isNaN(current) || Number.isNaN(floor) || current < floor)
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* emission-guard —— 审查建议的防噪闸(对照 oh-my-pi AdvisorEmissionGuard,
|
|
3
|
+
* 其源于生产事故:某会话 advisor 发了 309 次 advise、其中 114 次是
|
|
4
|
+
* "Stop."——防噪规则必须是代码而不是提示词)。
|
|
5
|
+
*
|
|
6
|
+
* 我们对应的问题:巡逻重复发同类 CORRECTION(实测"已咨询过不要重复"
|
|
7
|
+
* 连续纠偏)、无理由的"停止"式裁决。三道闸:
|
|
8
|
+
* 1. 归一化:大小写/NFKC/非字母数字折叠——"Stop."/"*STOP*"/" 停止!"
|
|
9
|
+
* 归一到同一个键;
|
|
10
|
+
* 2. 内容空短语黑名单:归一化后整体匹配(中英双语)——只说结论不给
|
|
11
|
+
* 理由的建议没有信息量,按 unclear 处理不打扰;
|
|
12
|
+
* 3. 会话内精确去重:已注入过的建议再次出现直接丢弃(FIFO 容量上限,
|
|
13
|
+
* 对齐 OMP 的 4096)。
|
|
14
|
+
*/
|
|
15
|
+
/** 会话内建议去重的历史容量(对齐 oh-my-pi) */
|
|
16
|
+
export declare const ADVICE_DEDUPE_CAPACITY = 4096;
|
|
17
|
+
/**
|
|
18
|
+
* 归一化建议文本(纯函数,可单测):小写 + NFKC + 非字母数字连续段折叠为
|
|
19
|
+
* 单空格 + 修剪。\p{L} 覆盖 CJK 表意字符,中文建议同样可归一。
|
|
20
|
+
*/
|
|
21
|
+
export declare function normalizeAdvice(text: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* 归一化后的建议是否"内容空"(纯函数):整体命中黑名单、或归一化后为空。
|
|
24
|
+
* 部分匹配不算——"停止:await 缺失会丢缓冲写"这类带理由的建议不受影响。
|
|
25
|
+
*/
|
|
26
|
+
export declare function isContentFreeAdvice(normalized: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* 会话内建议去重器(每 agent 一份)。返回一个判定函数:输入归一化后的
|
|
29
|
+
* 建议键,首次出现返回 true(接受并记录),重复出现返回 false(丢弃)。
|
|
30
|
+
* FIFO 容量上限防止长会话无界增长。
|
|
31
|
+
*/
|
|
32
|
+
export declare function createAdviceDeduper(capacity?: number): (normalized: string) => boolean;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* emission-guard —— 审查建议的防噪闸(对照 oh-my-pi AdvisorEmissionGuard,
|
|
3
|
+
* 其源于生产事故:某会话 advisor 发了 309 次 advise、其中 114 次是
|
|
4
|
+
* "Stop."——防噪规则必须是代码而不是提示词)。
|
|
5
|
+
*
|
|
6
|
+
* 我们对应的问题:巡逻重复发同类 CORRECTION(实测"已咨询过不要重复"
|
|
7
|
+
* 连续纠偏)、无理由的"停止"式裁决。三道闸:
|
|
8
|
+
* 1. 归一化:大小写/NFKC/非字母数字折叠——"Stop."/"*STOP*"/" 停止!"
|
|
9
|
+
* 归一到同一个键;
|
|
10
|
+
* 2. 内容空短语黑名单:归一化后整体匹配(中英双语)——只说结论不给
|
|
11
|
+
* 理由的建议没有信息量,按 unclear 处理不打扰;
|
|
12
|
+
* 3. 会话内精确去重:已注入过的建议再次出现直接丢弃(FIFO 容量上限,
|
|
13
|
+
* 对齐 OMP 的 4096)。
|
|
14
|
+
*/
|
|
15
|
+
/** 会话内建议去重的历史容量(对齐 oh-my-pi) */
|
|
16
|
+
export const ADVICE_DEDUPE_CAPACITY = 4_096;
|
|
17
|
+
/**
|
|
18
|
+
* 归一化建议文本(纯函数,可单测):小写 + NFKC + 非字母数字连续段折叠为
|
|
19
|
+
* 单空格 + 修剪。\p{L} 覆盖 CJK 表意字符,中文建议同样可归一。
|
|
20
|
+
*/
|
|
21
|
+
export function normalizeAdvice(text) {
|
|
22
|
+
return text
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.normalize('NFKC')
|
|
25
|
+
.replace(/[^\p{L}\p{N}]+/gu, ' ')
|
|
26
|
+
.trim();
|
|
27
|
+
}
|
|
28
|
+
/** 归一化后整体匹配的"内容空"短语——只给结论不给理由(黑名单保守收录) */
|
|
29
|
+
const CONTENT_FREE_PHRASES = new Set([
|
|
30
|
+
// 英文(对齐 OMP 观测到的噪声)
|
|
31
|
+
'stop', 'stop here', 'stop now', 'halt', 'abort',
|
|
32
|
+
'done', 'task done', 'task complete', 'complete', 'finished', 'ok', 'okay', 'ok done',
|
|
33
|
+
'no issue', 'no issues', 'no issue continue', 'no concerns', 'no concern',
|
|
34
|
+
'nothing to add', 'nothing to flag', 'nothing to report', 'no notes',
|
|
35
|
+
'no further input', 'no further input needed', 'no further input required',
|
|
36
|
+
'lgtm', 'looks good', 'looks good to me', 'continue', 'keep going',
|
|
37
|
+
// 中文(patrol 裁决实测可能出现的形式;键必须是归一化后的形态——
|
|
38
|
+
// 标点已折叠为空格,如"没有问题,继续。"→"没有问题 继续")
|
|
39
|
+
'停止', '停止吧', '立即停止', '停下来', '停下', '中止', '终止',
|
|
40
|
+
'已完成', '已完成任务', '任务完成', '任务已完成', '完成', '结束了',
|
|
41
|
+
'好的', '没有问题', '无问题', '没有问题 继续', '无需修改', '不需要修改',
|
|
42
|
+
'继续', '继续保持', '保持现状', '一切正常', '一切正常 继续', '方向正确',
|
|
43
|
+
'没有建议', '无建议', '没有要补充的', '无补充', '没有意见',
|
|
44
|
+
]);
|
|
45
|
+
/**
|
|
46
|
+
* 归一化后的建议是否"内容空"(纯函数):整体命中黑名单、或归一化后为空。
|
|
47
|
+
* 部分匹配不算——"停止:await 缺失会丢缓冲写"这类带理由的建议不受影响。
|
|
48
|
+
*/
|
|
49
|
+
export function isContentFreeAdvice(normalized) {
|
|
50
|
+
if (normalized === '')
|
|
51
|
+
return true;
|
|
52
|
+
return CONTENT_FREE_PHRASES.has(normalized);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 会话内建议去重器(每 agent 一份)。返回一个判定函数:输入归一化后的
|
|
56
|
+
* 建议键,首次出现返回 true(接受并记录),重复出现返回 false(丢弃)。
|
|
57
|
+
* FIFO 容量上限防止长会话无界增长。
|
|
58
|
+
*/
|
|
59
|
+
export function createAdviceDeduper(capacity = ADVICE_DEDUPE_CAPACITY) {
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
const order = [];
|
|
62
|
+
return (normalized) => {
|
|
63
|
+
if (seen.has(normalized))
|
|
64
|
+
return false;
|
|
65
|
+
seen.add(normalized);
|
|
66
|
+
order.push(normalized);
|
|
67
|
+
if (order.length > capacity) {
|
|
68
|
+
const oldest = order.shift();
|
|
69
|
+
if (oldest !== undefined)
|
|
70
|
+
seen.delete(oldest);
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
};
|
|
74
|
+
}
|
package/lib/gating.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gating —— 按 agent 动态可见性。每次解析模型请求时把执行路由对照
|
|
3
|
+
* 黑名单:命中的 agent 在自己的作用域上挂 restrict({deny:['advisor']})
|
|
4
|
+
* (工具 schema 从该 agent 的提示里消失),未命中的解除限制。
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
import { type Config } from './config.js';
|
|
8
|
+
export declare function registerGating(ctx: Context, getConfig: () => Config, hasReviewer: () => boolean): () => void;
|