dsh-desktop-kit 2.2.1 → 2.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +114 -27
- package/client.js +3270 -32
- package/docs/AGENT-HARNESS.md +144 -0
- package/docs/ARCHITECTURE.md +38 -7
- package/docs/COMPATIBILITY.md +4 -2
- package/docs/DEVELOPMENT.md +56 -0
- package/docs/INSTALL.md +20 -11
- package/docs/LAPTOP-INSTALL.md +96 -0
- package/docs/ROADMAP-3.0.md +353 -0
- package/docs/ROADMAP.md +1 -1
- package/docs/SUBAGENTS.md +25 -0
- package/docs/TEST-0.2.2.md +5 -5
- package/docs/TEST-2.3.0.md +49 -0
- package/docs/TEST-2.7.2.md +46 -0
- package/docs/TEST-2.7.3.md +20 -0
- package/docs/TOKENS.md +2 -2
- package/docs/TROUBLESHOOTING.md +27 -0
- package/docs/UI-QA.md +28 -0
- package/docs/images/peak-toast-peak.png +0 -0
- package/docs/images/peak-toast-soon.png +0 -0
- package/docs/preview/peak-toast.html +155 -0
- package/index.js +18 -1
- package/package.json +26 -6
- package/plugins/dsh-agent-extensions/README.md +107 -0
- package/plugins/dsh-agent-extensions/client.js +157 -0
- package/plugins/dsh-agent-extensions/index.js +174 -0
- package/plugins/dsh-agent-extensions/lib/agents.js +76 -0
- package/plugins/dsh-agent-extensions/lib/api.js +53 -0
- package/plugins/dsh-agent-extensions/lib/checkpoints.js +103 -0
- package/plugins/dsh-agent-extensions/lib/commands.js +255 -0
- package/plugins/dsh-agent-extensions/lib/hooks.js +182 -0
- package/plugins/dsh-agent-extensions/lib/policy.js +84 -0
- package/plugins/dsh-agent-extensions/lib/project.js +144 -0
- package/plugins/dsh-agent-extensions/lib/scaffold.js +151 -0
- package/plugins/dsh-agent-extensions/package.json +20 -0
- package/plugins/dsh-agent-extensions/screenshots/agents-1440.png +0 -0
- package/plugins/dsh-agent-extensions/screenshots/agents-390.png +0 -0
- package/plugins/dsh-browser-qa/README.md +221 -0
- package/plugins/dsh-browser-qa/cordis.patch.yml +4 -0
- package/plugins/dsh-browser-qa/index.js +165 -0
- package/plugins/dsh-browser-qa/lib/cdp.js +224 -0
- package/plugins/dsh-browser-qa/lib/chrome.js +466 -0
- package/plugins/dsh-browser-qa/lib/pages.js +552 -0
- package/plugins/dsh-browser-qa/lib/qa.js +827 -0
- package/plugins/dsh-browser-qa/lib/ui-gate.js +256 -0
- package/plugins/dsh-browser-qa/package.json +31 -0
- package/plugins/dsh-checkpoints/README.md +22 -0
- package/plugins/dsh-checkpoints/cordis.patch.yml +3 -0
- package/plugins/dsh-checkpoints/index.js +80 -0
- package/plugins/dsh-checkpoints/lib/checkpoints.js +111 -0
- package/plugins/dsh-checkpoints/package.json +25 -0
- package/plugins/dsh-github-review/README.md +131 -0
- package/plugins/dsh-github-review/cordis.patch.yml +3 -0
- package/plugins/dsh-github-review/index.js +536 -0
- package/plugins/dsh-github-review/lib/gh.js +494 -0
- package/plugins/dsh-github-review/lib/normalize.js +662 -0
- package/plugins/dsh-github-review/package.json +28 -0
- package/plugins/dsh-guardrails/README.md +264 -0
- package/plugins/dsh-guardrails/cordis.patch.yml +5 -0
- package/plugins/dsh-guardrails/index.js +389 -0
- package/plugins/dsh-guardrails/lib/hooks.js +262 -0
- package/plugins/dsh-guardrails/lib/modes.js +77 -0
- package/plugins/dsh-guardrails/lib/policy.js +592 -0
- package/plugins/dsh-guardrails/lib/scan.js +451 -0
- package/plugins/dsh-guardrails/lib/secrets.js +274 -0
- package/plugins/dsh-guardrails/package.json +26 -0
- package/plugins/dsh-mcp-manager/README.md +78 -0
- package/plugins/dsh-mcp-manager/client.js +373 -0
- package/plugins/dsh-mcp-manager/index.js +159 -0
- package/plugins/dsh-mcp-manager/lib/api.js +250 -0
- package/plugins/dsh-mcp-manager/lib/client.js +219 -0
- package/plugins/dsh-mcp-manager/lib/config.js +217 -0
- package/plugins/dsh-mcp-manager/lib/manager.js +146 -0
- package/plugins/dsh-mcp-manager/package.json +18 -0
- package/plugins/dsh-mcp-manager/screenshots/modal-390.png +0 -0
- package/plugins/dsh-mcp-manager/screenshots/settings-1440.png +0 -0
- package/plugins/dsh-mcp-manager/screenshots/settings-390.png +0 -0
- package/plugins/dsh-memory-bridge/README.md +161 -0
- package/plugins/dsh-memory-bridge/client.js +2264 -0
- package/plugins/dsh-memory-bridge/cordis.patch.yml +5 -0
- package/plugins/dsh-memory-bridge/docs/images/conflicts.png +0 -0
- package/plugins/dsh-memory-bridge/docs/images/hero.png +0 -0
- package/plugins/dsh-memory-bridge/docs/images/settings-dark.png +0 -0
- package/plugins/dsh-memory-bridge/docs/images/settings-light.png +0 -0
- package/plugins/dsh-memory-bridge/docs/images/settings-mobile.png +0 -0
- package/plugins/dsh-memory-bridge/docs/images/setup.png +0 -0
- package/plugins/dsh-memory-bridge/icon.svg +13 -0
- package/plugins/dsh-memory-bridge/index.js +378 -0
- package/plugins/dsh-memory-bridge/lib/bridge.js +636 -0
- package/plugins/dsh-memory-bridge/lib/github.js +473 -0
- package/plugins/dsh-memory-bridge/lib/merge.js +205 -0
- package/plugins/dsh-memory-bridge/lib/portable.js +314 -0
- package/plugins/dsh-memory-bridge/lib/state.js +565 -0
- package/plugins/dsh-memory-bridge/lib/sync.js +984 -0
- package/plugins/dsh-memory-bridge/locale/en.json +6 -0
- package/plugins/dsh-memory-bridge/locale/ru.json +6 -0
- package/plugins/dsh-memory-bridge/package.json +67 -0
- package/plugins/dsh-peak-guard/README.md +16 -3
- package/plugins/dsh-peak-guard/client.js +223 -8
- package/plugins/dsh-peak-guard/package.json +1 -1
- package/plugins/dsh-plan-mode/README.md +24 -0
- package/plugins/dsh-plan-mode/cordis.patch.yml +3 -0
- package/plugins/dsh-plan-mode/index.js +103 -0
- package/plugins/dsh-plan-mode/lib/plan.js +89 -0
- package/plugins/dsh-plan-mode/package.json +18 -0
- package/plugins/dsh-profile-transfer/client.js +277 -17
- package/plugins/dsh-profile-transfer/index.js +10 -0
- package/plugins/dsh-profile-transfer/package.json +3 -1
- package/plugins/dsh-profile-transfer/transfer.js +101 -2
- package/plugins/dsh-project-memory/index.js +92 -2
- package/plugins/dsh-project-memory/package.json +1 -1
- package/plugins/dsh-repo-map/README.md +26 -0
- package/plugins/dsh-repo-map/cordis.patch.yml +3 -0
- package/plugins/dsh-repo-map/index.js +99 -0
- package/plugins/dsh-repo-map/lib/repo-map.js +170 -0
- package/plugins/dsh-repo-map/package.json +25 -0
- package/plugins/dsh-skill-autopilot/index.js +135 -11
- package/skills/delivery-gate/SKILL.md +31 -0
- package/skills/git-and-ci/SKILL.md +5 -1
- package/skills/manifest.json +31 -6
- package/skills/requirements-and-planning/SKILL.md +3 -0
- package/skills/research-first/SKILL.md +35 -0
- package/skills/research-first/references/query-patterns.md +20 -0
- package/skills/subagent-playbook/SKILL.md +36 -0
- package/skills/subagent-playbook/references/prompts.md +39 -0
- package/skills/ui-craft/SKILL.md +43 -0
- package/skills/visual-qa/SKILL.md +29 -0
- package/skills/visual-qa/references/commands.md +32 -0
- package/skills-baseline.json +219 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"title": "dsh-memory-bridge",
|
|
4
|
+
"description": "GitHub-only synchronisation of DSH project-memory dossiers between workstations through your own private repository: every machine signs in with its own GitHub account (gh CLI), three-way merge, conflicts resolved by mouse in Settings, no tokens, no commands needed."
|
|
5
|
+
}
|
|
6
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"title": "dsh-memory-bridge",
|
|
4
|
+
"description": "Синхронизация досье памяти проектов между АРМ через ваш собственный приватный GitHub-репозиторий: на каждой машине — свой вход в GitHub (gh CLI), трёхстороннее слияние, конфликты разрешаются мышью в настройках, без токенов и команд."
|
|
5
|
+
}
|
|
6
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-memory-bridge",
|
|
3
|
+
"version": "2.3.1",
|
|
4
|
+
"description": "GitHub-only synchronisation of DSH project-memory dossiers between workstations through each user's own private repository: three-way merge, mouse-only Settings page, no commands needed.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "dsh-memory-bridge contributors",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Bulavackii/dsh-memory-bridge.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/Bulavackii/dsh-memory-bridge#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Bulavackii/dsh-memory-bridge/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"deepseek-harness",
|
|
17
|
+
"dsh",
|
|
18
|
+
"plugin",
|
|
19
|
+
"project-memory",
|
|
20
|
+
"github",
|
|
21
|
+
"sync",
|
|
22
|
+
"merge",
|
|
23
|
+
"skills"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"main": "index.js",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": "./index.js",
|
|
29
|
+
"./client": "./client.js",
|
|
30
|
+
"./package.json": "./package.json",
|
|
31
|
+
"./locale/*.json": "./locale/*.json",
|
|
32
|
+
"./icon": "./icon.svg"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"index.js",
|
|
36
|
+
"client.js",
|
|
37
|
+
"lib/**",
|
|
38
|
+
"cordis.patch.yml",
|
|
39
|
+
"locale/*.json",
|
|
40
|
+
"icon.svg",
|
|
41
|
+
"docs/images/**",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"test": "node --test"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=20"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"dsh": {
|
|
55
|
+
"manifestVersion": 1,
|
|
56
|
+
"bundle": {
|
|
57
|
+
"patch": "./cordis.patch.yml"
|
|
58
|
+
},
|
|
59
|
+
"client": {
|
|
60
|
+
"platform": "web",
|
|
61
|
+
"immediately": true,
|
|
62
|
+
"inject": []
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
# dsh-peak-guard — живое уведомление о пиковом тарифе DeepSeek
|
|
2
2
|
|
|
3
3
|
Клиентский плагин для DeepSeek Harness: пока действует пиковый тариф (токены ×2),
|
|
4
|
-
|
|
5
|
-
компактный бейдж «Пик ×2 · осталось N мин».
|
|
4
|
+
**в правом нижнем углу всплывает премиальное уведомление**, а в чате над композером
|
|
5
|
+
показывается карточка, в заголовке сессии — компактный бейдж «Пик ×2 · осталось N мин».
|
|
6
|
+
|
|
7
|
+
## Угловое уведомление 1.3
|
|
8
|
+
|
|
9
|
+
- плавно появляется в правом нижнем углу (`shell.overlay`, поверх всего интерфейса);
|
|
10
|
+
- показывает причину, текущее окно, живой обратный отсчёт и тонкую полосу прогресса;
|
|
11
|
+
- за 5 минут до пика — янтарное состояние «Скоро пик», в пике — тёплое красное;
|
|
12
|
+
- **«Подробнее»** раскрывает причину, расписание и примечание о праздниках Китая;
|
|
13
|
+
- авто-скрывается через 18 секунд, чтобы не мешать; карточка в чате остаётся;
|
|
14
|
+
- «Скрыть» запоминает окно; `Esc` закрывает; на 390 px занимает низ экрана;
|
|
15
|
+
- `prefers-reduced-motion` отключает анимации; `role=status`, `aria-live=polite`.
|
|
6
16
|
|
|
7
17
|
## Что показывает уведомление
|
|
8
18
|
|
|
@@ -37,7 +47,10 @@
|
|
|
37
47
|
| Дневная | 06:00–10:00 | 09:00–13:00 | 14:00–18:00 |
|
|
38
48
|
|
|
39
49
|
Всё остальное время — off-peak со скидкой 50%. Будни определяются по UTC; выходные
|
|
40
|
-
пика не имеют.
|
|
50
|
+
пика не имеют. **Государственные праздники Китая — тоже off-peak:** в плагин встроен
|
|
51
|
+
официальный календарь 2026–2027 (元旦, 春节, 清明, 劳动节, 端午, 中秋, 国庆), поэтому
|
|
52
|
+
праздничные будни не считаются пиком. Даты календаря сверены 09.11.2026; при объявлении
|
|
53
|
+
нового календаря список `HOLIDAY_RANGES` обновляется.
|
|
41
54
|
|
|
42
55
|
## Реальное время
|
|
43
56
|
|
|
@@ -33,6 +33,26 @@ window.__ModuleLoader__.load({
|
|
|
33
33
|
{ from: 1, to: 4, id: "morning" },
|
|
34
34
|
{ from: 6, to: 10, id: "day" },
|
|
35
35
|
];
|
|
36
|
+
// Official DeepSeek peak windows (01:00–04:00 and 06:00–10:00 UTC, Mon–Fri)
|
|
37
|
+
// exclude Chinese public holidays. Ranges are Beijing local dates, from the
|
|
38
|
+
// State Council 2026/2027 holiday notices. Weekends are off-peak by rule.
|
|
39
|
+
const HOLIDAY_RANGES = [
|
|
40
|
+
["2026-01-01", "2026-01-03"],
|
|
41
|
+
["2026-02-15", "2026-02-23"],
|
|
42
|
+
["2026-04-04", "2026-04-06"],
|
|
43
|
+
["2026-05-01", "2026-05-05"],
|
|
44
|
+
["2026-06-19", "2026-06-21"],
|
|
45
|
+
["2026-09-25", "2026-09-27"],
|
|
46
|
+
["2026-10-01", "2026-10-07"],
|
|
47
|
+
["2027-01-01", "2027-01-03"],
|
|
48
|
+
["2027-02-05", "2027-02-13"],
|
|
49
|
+
["2027-04-03", "2027-04-05"],
|
|
50
|
+
["2027-05-01", "2027-05-05"],
|
|
51
|
+
["2027-06-09", "2027-06-09"],
|
|
52
|
+
["2027-09-15", "2027-09-15"],
|
|
53
|
+
["2027-10-01", "2027-10-07"],
|
|
54
|
+
];
|
|
55
|
+
const SCHEDULE_VERIFIED = "2026-11-09";
|
|
36
56
|
const TICK_MS = 1000;
|
|
37
57
|
const PEAK_LEAD_MS = 5 * 60 * 1000;
|
|
38
58
|
const DISMISS_KEY = "dsh-peak-guard:dismissed-window-end";
|
|
@@ -44,7 +64,7 @@ window.__ModuleLoader__.load({
|
|
|
44
64
|
const EN = {
|
|
45
65
|
badgeLabel: "Peak ×2",
|
|
46
66
|
badgeTitle: "Peak pricing is active now: tokens cost 2×. {wait} left in this window.",
|
|
47
|
-
soonBadgeLabel: "Peak in {wait}",
|
|
67
|
+
soonBadgeLabel: "Peak in {wait}",
|
|
48
68
|
nowLabel: "Now",
|
|
49
69
|
kicker: "Peak pricing",
|
|
50
70
|
soonKicker: "Peak coming",
|
|
@@ -70,7 +90,7 @@ window.__ModuleLoader__.load({
|
|
|
70
90
|
scheduleChipLabel: "Peak windows",
|
|
71
91
|
scheduleUtc: "UTC: {windows}",
|
|
72
92
|
scheduleLocal: "Your time ({zone}): {windows}",
|
|
73
|
-
holidayNote: "Chinese public holidays
|
|
93
|
+
holidayNote: "Peak windows skip weekends and Chinese public holidays (2026–2027 calendar is embedded); all other hours are off-peak.",
|
|
74
94
|
dismiss: "Hide for this peak",
|
|
75
95
|
soonDismiss: "Hide until the peak starts",
|
|
76
96
|
details: "Schedule",
|
|
@@ -78,11 +98,21 @@ window.__ModuleLoader__.load({
|
|
|
78
98
|
waitHours: "{hours} h {minutes} min",
|
|
79
99
|
waitMinutes: "{minutes} min",
|
|
80
100
|
waitSeconds: "{seconds} s",
|
|
101
|
+
toastKicker: "Peak pricing",
|
|
102
|
+
toastTitle: "Tokens cost double right now",
|
|
103
|
+
toastSoonTitle: "Peak pricing starts in {wait}",
|
|
104
|
+
toastMeta: "{wait} left · {start}–{end} {zone}",
|
|
105
|
+
toastSoonMeta: "starts in {wait} · {start}–{end} {zone}",
|
|
106
|
+
toastSchedule: "Schedule",
|
|
107
|
+
toastHide: "Hide",
|
|
108
|
+
toastDetails: "Details",
|
|
109
|
+
toastVerified: "Peak schedule verified {date}",
|
|
110
|
+
toastAria: "Peak pricing warning",
|
|
81
111
|
};
|
|
82
112
|
const RU = {
|
|
83
113
|
badgeLabel: "Пик ×2",
|
|
84
114
|
badgeTitle: "Сейчас пиковый тариф: токены ×2. До конца окна осталось {wait}.",
|
|
85
|
-
soonBadgeLabel: "Пик через {wait}",
|
|
115
|
+
soonBadgeLabel: "Пик через {wait}",
|
|
86
116
|
nowLabel: "Сейчас",
|
|
87
117
|
kicker: "Пиковый тариф",
|
|
88
118
|
soonKicker: "Скоро пик",
|
|
@@ -108,7 +138,7 @@ window.__ModuleLoader__.load({
|
|
|
108
138
|
scheduleChipLabel: "Окна пика",
|
|
109
139
|
scheduleUtc: "UTC: {windows}",
|
|
110
140
|
scheduleLocal: "По вашему времени ({zone}): {windows}",
|
|
111
|
-
holidayNote: "
|
|
141
|
+
holidayNote: "Пиковые окна не действуют по выходным и в государственные праздники Китая (календарь 2026–2027 встроен); всё остальное время — off-peak.",
|
|
112
142
|
dismiss: "Скрыть на этот пик",
|
|
113
143
|
soonDismiss: "Скрыть до начала пика",
|
|
114
144
|
details: "Расписание",
|
|
@@ -116,11 +146,21 @@ window.__ModuleLoader__.load({
|
|
|
116
146
|
waitHours: "{hours} ч {minutes} мин",
|
|
117
147
|
waitMinutes: "{minutes} мин",
|
|
118
148
|
waitSeconds: "{seconds} с",
|
|
149
|
+
toastKicker: "Пиковый тариф",
|
|
150
|
+
toastTitle: "Токены сейчас стоят вдвое дороже",
|
|
151
|
+
toastSoonTitle: "Пиковый тариф через {wait}",
|
|
152
|
+
toastMeta: "осталось {wait} · {start}–{end} {zone}",
|
|
153
|
+
toastSoonMeta: "начнётся через {wait} · {start}–{end} {zone}",
|
|
154
|
+
toastSchedule: "Расписание",
|
|
155
|
+
toastHide: "Скрыть",
|
|
156
|
+
toastDetails: "Подробнее",
|
|
157
|
+
toastVerified: "Расписание проверено {date}",
|
|
158
|
+
toastAria: "Предупреждение о пиковом тарифе",
|
|
119
159
|
};
|
|
120
160
|
const ZH = {
|
|
121
161
|
badgeLabel: "高峰 ×2",
|
|
122
162
|
badgeTitle: "当前为高峰时段:Token 价格 ×2。本时段还剩 {wait}。",
|
|
123
|
-
soonBadgeLabel: "高峰还剩 {wait}",
|
|
163
|
+
soonBadgeLabel: "高峰还剩 {wait}",
|
|
124
164
|
nowLabel: "现在",
|
|
125
165
|
kicker: "高峰价格",
|
|
126
166
|
soonKicker: "高峰将至",
|
|
@@ -146,7 +186,7 @@ window.__ModuleLoader__.load({
|
|
|
146
186
|
scheduleChipLabel: "高峰时段",
|
|
147
187
|
scheduleUtc: "UTC:{windows}",
|
|
148
188
|
scheduleLocal: "本地时间({zone}):{windows}",
|
|
149
|
-
holidayNote: "
|
|
189
|
+
holidayNote: "高峰时段不包含周末与中国法定节假日(已内置 2026–2027 日历);其余时间均为错峰价。",
|
|
150
190
|
dismiss: "本次高峰不再提示",
|
|
151
191
|
soonDismiss: "高峰开始前不再提示",
|
|
152
192
|
details: "时间表",
|
|
@@ -154,6 +194,16 @@ window.__ModuleLoader__.load({
|
|
|
154
194
|
waitHours: "{hours} 小时 {minutes} 分钟",
|
|
155
195
|
waitMinutes: "{minutes} 分钟",
|
|
156
196
|
waitSeconds: "{seconds} 秒",
|
|
197
|
+
toastKicker: "高峰价格",
|
|
198
|
+
toastTitle: "当前 Token 价格为 2 倍",
|
|
199
|
+
toastSoonTitle: "高峰价格将在 {wait} 后开始",
|
|
200
|
+
toastMeta: "还剩 {wait} · {start}–{end} {zone}",
|
|
201
|
+
toastSoonMeta: "将在 {wait} 后开始 · {start}–{end} {zone}",
|
|
202
|
+
toastSchedule: "时间表",
|
|
203
|
+
toastHide: "隐藏",
|
|
204
|
+
toastDetails: "详情",
|
|
205
|
+
toastVerified: "价格时段已核对 {date}",
|
|
206
|
+
toastAria: "高峰价格提醒",
|
|
157
207
|
};
|
|
158
208
|
|
|
159
209
|
const inject = ["slots", "locale"];
|
|
@@ -208,8 +258,20 @@ window.__ModuleLoader__.load({
|
|
|
208
258
|
return `${formatClock(start)}–${formatClock(end)}`;
|
|
209
259
|
}
|
|
210
260
|
|
|
261
|
+
/** Beijing calendar date key; Chinese holiday ranges are defined in local dates. */
|
|
262
|
+
function beijingDateKey(date) {
|
|
263
|
+
const local = new Date(date.getTime() + 8 * 3600 * 1000);
|
|
264
|
+
return `${local.getUTCFullYear()}-${pad(local.getUTCMonth() + 1)}-${pad(local.getUTCDate())}`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function isChineseHoliday(date) {
|
|
268
|
+
const key = beijingDateKey(date);
|
|
269
|
+
return HOLIDAY_RANGES.some(([from, to]) => key >= from && key <= to);
|
|
270
|
+
}
|
|
271
|
+
|
|
211
272
|
/** Peak window containing `date`, or null. Weekdays are evaluated in UTC. */
|
|
212
273
|
function peakWindowAt(date) {
|
|
274
|
+
if (isChineseHoliday(date)) return null;
|
|
213
275
|
const day = date.getUTCDay();
|
|
214
276
|
if (day === 0 || day === 6) return null;
|
|
215
277
|
const minutes = date.getUTCHours() * 60 + date.getUTCMinutes() + date.getUTCSeconds() / 60;
|
|
@@ -231,9 +293,9 @@ window.__ModuleLoader__.load({
|
|
|
231
293
|
/** Next peak window start strictly after `date`. */
|
|
232
294
|
function nextPeakWindow(date) {
|
|
233
295
|
const probe = new Date(date.getTime());
|
|
234
|
-
for (let index = 0; index <
|
|
296
|
+
for (let index = 0; index < 20; index += 1) {
|
|
235
297
|
const day = probe.getUTCDay();
|
|
236
|
-
if (day !== 0 && day !== 6) {
|
|
298
|
+
if (day !== 0 && day !== 6 && !isChineseHoliday(probe)) {
|
|
237
299
|
for (const window of WINDOWS_UTC) {
|
|
238
300
|
const start = Date.UTC(probe.getUTCFullYear(), probe.getUTCMonth(), probe.getUTCDate(), window.from, 0, 0);
|
|
239
301
|
if (start > date.getTime()) {
|
|
@@ -514,6 +576,37 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
514
576
|
.dpg-btn[data-emphasis="true"]{background:var(--dpg-red);color:#2a0709}
|
|
515
577
|
}
|
|
516
578
|
@media (max-width:1100px){.dpg-badge-wait{display:none}}
|
|
579
|
+
/* corner toast: a quiet, premium alert over the whole frame */
|
|
580
|
+
.dpg-toast-wrap{position:fixed;right:22px;bottom:20px;z-index:1300;max-width:min(392px,calc(100vw - 24px));pointer-events:none}
|
|
581
|
+
.dpg-toast{--dpg-toast-accent:var(--dsw-alias-state-error-primary,#e5484d);position:relative;display:flex;flex-direction:column;gap:9px;padding:13px 13px 11px;border-radius:17px;border:1px solid color-mix(in srgb,var(--dpg-toast-accent) 24%,transparent);background:linear-gradient(155deg,color-mix(in srgb,var(--dpg-toast-accent) 6%,var(--dsw-alias-bg-layer-1,#fff)) 0%,var(--dsw-alias-bg-layer-1,#fff) 62%);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff 50%,transparent),0 22px 48px -28px color-mix(in srgb,var(--dpg-toast-accent) 78%,transparent);color:var(--dsw-alias-label-primary,#111);pointer-events:auto;animation:dpg-toast-in .32s cubic-bezier(.16,1,.3,1)}
|
|
582
|
+
.dpg-toast[data-mode="soon"]{--dpg-toast-accent:var(--dsw-alias-state-warn-primary,#d97706)}
|
|
583
|
+
.dpg-toast::before{content:"";position:absolute;left:0;top:14px;bottom:14px;width:3px;border-radius:0 3px 3px 0;background:var(--dpg-toast-accent)}
|
|
584
|
+
.dpg-toast-head{display:flex;align-items:flex-start;gap:10px;padding-left:5px}
|
|
585
|
+
.dpg-toast-icon{flex:none;width:34px;height:34px;border-radius:11px;display:flex;align-items:center;justify-content:center;color:#fff;background:linear-gradient(150deg,var(--dpg-toast-accent),color-mix(in srgb,var(--dpg-toast-accent) 55%,#4a070c));box-shadow:0 7px 16px -9px color-mix(in srgb,var(--dpg-toast-accent) 95%,transparent),inset 0 1px 0 rgba(255,255,255,.3)}
|
|
586
|
+
.dpg-toast[data-mode="soon"] .dpg-toast-icon{background:linear-gradient(150deg,var(--dpg-toast-accent),color-mix(in srgb,var(--dpg-toast-accent) 55%,#4a2c00));color:#fff}
|
|
587
|
+
.dpg-toast-copy{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1;padding-top:1px}
|
|
588
|
+
.dpg-toast-kicker{display:flex;align-items:center;gap:6px;font-size:9.5px;font-weight:750;letter-spacing:.14em;text-transform:uppercase;color:color-mix(in srgb,var(--dpg-toast-accent) 82%,#111)}
|
|
589
|
+
.dpg-toast-kicker .dpg-dot{width:5px;height:5px;box-shadow:0 0 0 3px color-mix(in srgb,var(--dpg-toast-accent) 13%,transparent)}
|
|
590
|
+
.dpg-toast-chip{display:inline-flex;align-items:center;height:15px;padding:0 5px;border-radius:4px;border:1px solid color-mix(in srgb,var(--dpg-toast-accent) 40%,transparent);background:color-mix(in srgb,var(--dpg-toast-accent) 9%,transparent);font-size:9px;font-weight:800;letter-spacing:.03em}
|
|
591
|
+
.dpg-toast-title{font-size:13.5px;line-height:18px;font-weight:660;letter-spacing:-.01em}
|
|
592
|
+
.dpg-toast-meta{font-size:11px;line-height:15px;color:var(--dsw-alias-label-secondary,#5b6472);font-variant-numeric:tabular-nums}
|
|
593
|
+
.dpg-toast-close{flex:none;border:none;background:transparent;color:var(--dsw-alias-label-secondary,#6b7280);width:24px;height:24px;border-radius:7px;display:flex;align-items:center;justify-content:center;font-size:15px;line-height:1;cursor:pointer;margin:-2px -2px 0 0}
|
|
594
|
+
.dpg-toast-close:hover{background:color-mix(in srgb,var(--dpg-toast-accent) 9%,transparent);color:var(--dpg-toast-accent)}
|
|
595
|
+
.dpg-toast-close:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2f7df6);outline-offset:1px}
|
|
596
|
+
.dpg-toast-track{position:relative;height:3px;border-radius:2px;background:color-mix(in srgb,var(--dpg-toast-accent) 13%,transparent);overflow:hidden;margin:0 1px}
|
|
597
|
+
.dpg-toast-fill{height:100%;border-radius:2px;background:linear-gradient(90deg,color-mix(in srgb,var(--dpg-toast-accent) 62%,#fff) 0%,var(--dpg-toast-accent) 100%);transition:width .9s linear}
|
|
598
|
+
.dpg-toast-actions{display:flex;align-items:center;gap:6px;padding-left:5px}
|
|
599
|
+
.dpg-toast-btn{border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.14));background:transparent;color:var(--dsw-alias-label-primary,#111);font:inherit;font-size:10.5px;font-weight:650;line-height:14px;padding:5px 9px;border-radius:7px;cursor:pointer;transition:background .16s ease,border-color .16s ease}
|
|
600
|
+
.dpg-toast-btn:hover{background:color-mix(in srgb,var(--dpg-toast-accent) 7%,transparent);border-color:color-mix(in srgb,var(--dpg-toast-accent) 34%,transparent)}
|
|
601
|
+
.dpg-toast-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2f7df6);outline-offset:1px}
|
|
602
|
+
.dpg-toast-btn[data-emphasis="true"]{border-color:transparent;background:var(--dpg-toast-accent);color:#fff}
|
|
603
|
+
.dpg-toast[data-mode="soon"] .dpg-toast-btn[data-emphasis="true"]{color:#2a1500}
|
|
604
|
+
.dpg-toast-details{display:grid;grid-template-columns:1fr;gap:5px;padding:7px 9px 2px;border-top:1px solid color-mix(in srgb,var(--dpg-toast-accent) 14%,transparent);font-size:10.5px;line-height:15px;color:var(--dsw-alias-label-secondary,#5b6472)}
|
|
605
|
+
.dpg-toast-details b{color:var(--dsw-alias-label-primary,#111);font-weight:650}
|
|
606
|
+
.dpg-toast-details .dpg-toast-schedule{display:flex;flex-wrap:wrap;gap:5px;margin-top:2px}
|
|
607
|
+
@keyframes dpg-toast-in{from{opacity:0;transform:translateY(10px) scale(.985)}to{opacity:1;transform:translateY(0) scale(1)}}
|
|
608
|
+
@media (max-width:520px){.dpg-toast-wrap{right:10px;left:10px;bottom:calc(10px + env(safe-area-inset-bottom,0px));max-width:none}.dpg-toast{max-height:70vh;overflow:auto;padding:12px 12px 10px}}
|
|
609
|
+
@media (prefers-reduced-motion:reduce){.dpg-toast{animation:none}.dpg-toast-fill{transition:none}}
|
|
517
610
|
@media (max-width:900px){.dpg-badge{display:none}.dpg-mid{align-items:stretch;flex-direction:column;gap:8px}.dpg-count{min-width:0;flex-direction:row;align-items:baseline;gap:8px}.dpg-count-label{margin-top:0}.dpg-meter{padding-bottom:0}.dpg-meter-head{flex-wrap:wrap;row-gap:2px}.dpg-tail{align-items:stretch;flex-direction:column}.dpg-actions{display:flex}.dpg-actions .dpg-btn{flex:1;text-align:center}.dpg-seal{width:52px;height:52px}.dpg-ring{width:52px;height:52px}.dpg-title{font-size:14.5px;line-height:20px}}
|
|
518
611
|
@media (max-width:520px){.dpg-card{position:fixed;left:10px;right:10px;bottom:calc(12px + env(safe-area-inset-bottom,0px));z-index:1200;max-height:78vh;overflow:auto;margin:0;padding:14px 15px 13px;gap:12px}.dpg-seal{display:none}.dpg-details{grid-template-columns:1fr}}
|
|
519
612
|
@media (prefers-reduced-motion:reduce){.dpg-card{animation:none}.dpg-dot,.dpg-seal-pulse{animation:none}.dpg-fill,.dpg-ring-fill{transition:none}}
|
|
@@ -690,6 +783,121 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
690
783
|
);
|
|
691
784
|
}
|
|
692
785
|
|
|
786
|
+
function PeakToast() {
|
|
787
|
+
const state = usePeakState(store, t);
|
|
788
|
+
const [dismissed, setDismissed] = useState(false);
|
|
789
|
+
const [autoHidden, setAutoHidden] = useState(false);
|
|
790
|
+
const [details, setDetails] = useState(false);
|
|
791
|
+
const active = state.mode === "peak" || state.mode === "soon";
|
|
792
|
+
const storageKey = state.mode === "peak" ? DISMISS_KEY : SOON_DISMISS_KEY;
|
|
793
|
+
const marker = state.mode === "peak" ? state.windowEnd : state.windowStart;
|
|
794
|
+
const readDismissed = () => {
|
|
795
|
+
try {
|
|
796
|
+
return Number(localStorage.getItem(storageKey) || "0") === Number(marker);
|
|
797
|
+
} catch {
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
useEffect(() => {
|
|
802
|
+
setDismissed(readDismissed());
|
|
803
|
+
setAutoHidden(false);
|
|
804
|
+
setDetails(false);
|
|
805
|
+
if (!active) return undefined;
|
|
806
|
+
const timer = setTimeout(() => setAutoHidden(true), 18000);
|
|
807
|
+
return () => clearTimeout(timer);
|
|
808
|
+
}, [state.mode, state.windowStart, state.windowEnd]);
|
|
809
|
+
useEffect(() => {
|
|
810
|
+
const onStorage = (event) => {
|
|
811
|
+
if (event.key === storageKey) setDismissed(readDismissed());
|
|
812
|
+
};
|
|
813
|
+
const onKey = (event) => {
|
|
814
|
+
if (event.key === "Escape" && active) setDismissed(true);
|
|
815
|
+
};
|
|
816
|
+
window.addEventListener("storage", onStorage);
|
|
817
|
+
window.addEventListener("keydown", onKey);
|
|
818
|
+
return () => {
|
|
819
|
+
window.removeEventListener("storage", onStorage);
|
|
820
|
+
window.removeEventListener("keydown", onKey);
|
|
821
|
+
};
|
|
822
|
+
}, [active, storageKey, state.mode, state.windowStart, state.windowEnd]);
|
|
823
|
+
if (!active || dismissed || autoHidden) return null;
|
|
824
|
+
const isSoon = state.mode === "soon";
|
|
825
|
+
const close = () => {
|
|
826
|
+
setDismissed(true);
|
|
827
|
+
try {
|
|
828
|
+
localStorage.setItem(storageKey, String(marker));
|
|
829
|
+
} catch {
|
|
830
|
+
/* private mode */
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
const progress = Math.round((state.progress ?? 0) * 100);
|
|
834
|
+
const meta = formatTemplate(isSoon ? t("toastSoonMeta") : t("toastMeta"), {
|
|
835
|
+
wait: state.wait,
|
|
836
|
+
start: state.startLabel,
|
|
837
|
+
end: state.endLabel,
|
|
838
|
+
zone: state.zone,
|
|
839
|
+
});
|
|
840
|
+
const title = isSoon ? formatTemplate(t("toastSoonTitle"), { wait: state.wait }) : t("toastTitle");
|
|
841
|
+
return h(
|
|
842
|
+
"div",
|
|
843
|
+
{ className: "dpg-toast-wrap" },
|
|
844
|
+
h(
|
|
845
|
+
"div",
|
|
846
|
+
{
|
|
847
|
+
className: "dpg-toast",
|
|
848
|
+
"data-mode": isSoon ? "soon" : "peak",
|
|
849
|
+
role: "status",
|
|
850
|
+
"aria-live": "polite",
|
|
851
|
+
"aria-atomic": "true",
|
|
852
|
+
"aria-label": t("toastAria"),
|
|
853
|
+
},
|
|
854
|
+
h(
|
|
855
|
+
"div",
|
|
856
|
+
{ className: "dpg-toast-head" },
|
|
857
|
+
h("span", { className: "dpg-toast-icon", "aria-hidden": "true" }, h(IconBolt)),
|
|
858
|
+
h(
|
|
859
|
+
"div",
|
|
860
|
+
{ className: "dpg-toast-copy" },
|
|
861
|
+
h(
|
|
862
|
+
"div",
|
|
863
|
+
{ className: "dpg-toast-kicker" },
|
|
864
|
+
h("span", { className: "dpg-dot", "aria-hidden": "true" }),
|
|
865
|
+
isSoon ? t("soonKicker") : t("toastKicker"),
|
|
866
|
+
h("span", { className: "dpg-toast-chip" }, t("multiplier")),
|
|
867
|
+
),
|
|
868
|
+
h("div", { className: "dpg-toast-title" }, title),
|
|
869
|
+
h("div", { className: "dpg-toast-meta" }, meta),
|
|
870
|
+
),
|
|
871
|
+
h("button", { type: "button", className: "dpg-toast-close", onClick: close, "aria-label": t("toastHide"), title: t("toastHide") }, "×"),
|
|
872
|
+
),
|
|
873
|
+
h(
|
|
874
|
+
"div",
|
|
875
|
+
{ className: "dpg-toast-track", role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": progress, "aria-label": isSoon ? t("soonCountdownLabel") : t("countdownLabel") },
|
|
876
|
+
h("span", { className: "dpg-toast-fill", style: { display: "block", width: `${progress}%` } }),
|
|
877
|
+
),
|
|
878
|
+
details
|
|
879
|
+
? h(
|
|
880
|
+
"div",
|
|
881
|
+
{ className: "dpg-toast-details" },
|
|
882
|
+
h("span", null, isSoon ? t("soonReason") : t("reason")),
|
|
883
|
+
h("span", { className: "dpg-toast-schedule" },
|
|
884
|
+
h("b", null, t("scheduleChipLabel")),
|
|
885
|
+
state.scheduleLocalParts.map((part) => h("span", { className: "dpg-chip", key: part }, part)),
|
|
886
|
+
),
|
|
887
|
+
h("span", null, t("holidayNote")),
|
|
888
|
+
h("span", null, formatTemplate(t("toastVerified"), { date: SCHEDULE_VERIFIED })),
|
|
889
|
+
)
|
|
890
|
+
: null,
|
|
891
|
+
h(
|
|
892
|
+
"div",
|
|
893
|
+
{ className: "dpg-toast-actions" },
|
|
894
|
+
h("button", { type: "button", className: "dpg-toast-btn", onClick: () => setDetails((value) => !value), "aria-expanded": details ? "true" : "false" }, details ? t("hideDetails") : t("toastDetails")),
|
|
895
|
+
h("button", { type: "button", className: "dpg-toast-btn", "data-emphasis": "true", onClick: close }, t("toastHide")),
|
|
896
|
+
),
|
|
897
|
+
),
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
|
|
693
901
|
ctx.effect(() => ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register({
|
|
694
902
|
name: "conversation.composer.dock",
|
|
695
903
|
id: "peak-guard",
|
|
@@ -697,6 +905,13 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
697
905
|
locale: NS,
|
|
698
906
|
}, PeakCard)), "peak-guard: composer dock card");
|
|
699
907
|
|
|
908
|
+
ctx.effect(() => ctx.slots.inject("shell.overlay", () => ctx.slots.register({
|
|
909
|
+
name: "shell.overlay",
|
|
910
|
+
id: "peak-guard-toast",
|
|
911
|
+
order: 70,
|
|
912
|
+
locale: NS,
|
|
913
|
+
}, PeakToast)), "peak-guard: corner toast");
|
|
914
|
+
|
|
700
915
|
ctx.effect(() => ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
|
|
701
916
|
name: "conversation.session.header.utilities",
|
|
702
917
|
id: "peak-guard",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-peak-guard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Red peak-pricing notice in the chat: when DeepSeek peak hours double the token price, a card explains the reason, the window in your local time, and how long to wait before continuing.",
|
|
6
6
|
"type": "module",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# dsh-plan-mode
|
|
2
|
+
|
|
3
|
+
План проекта в `.dsh/PLAN.md` с явным гейтом `draft → approved`.
|
|
4
|
+
|
|
5
|
+
## Инструмент `plan`
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
plan action=create content="# План\n1. ..."
|
|
9
|
+
plan action=status
|
|
10
|
+
plan action=read
|
|
11
|
+
plan action=update content="..."
|
|
12
|
+
plan action=approve confirm=true # только после явного согласия пользователя
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Пока план в статусе `draft`, плагин вставляет в системный промпт запрет на
|
|
16
|
+
редактирование кода и просит сначала согласовать план. После `approve` в промпт
|
|
17
|
+
попадает сам план, и можно выполнять шаги.
|
|
18
|
+
|
|
19
|
+
## Ограничения
|
|
20
|
+
|
|
21
|
+
- `approve` требует `confirm: true`; модель обязана сначала спросить пользователя.
|
|
22
|
+
- План — файл проекта; не храните в нём секреты.
|
|
23
|
+
- Это prompt-гейт, а не жёсткая блокировка инструментов: жёсткая блокировка —
|
|
24
|
+
в `dsh-guardrails` (pre-execute hooks).
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-plan-mode — PLAN.md with an explicit draft/approved gate.
|
|
3
|
+
*/
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import { approvePlan, readPlan, updatePlan, writePlan } from "./lib/plan.js";
|
|
7
|
+
|
|
8
|
+
export const inject = ["tools", "systemPrompt"];
|
|
9
|
+
|
|
10
|
+
function cwdOf(exec) {
|
|
11
|
+
const cwd = exec?.agent?.session?.header?.cwd;
|
|
12
|
+
return path.resolve(typeof cwd === "string" && cwd.length > 0 ? cwd : process.cwd());
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function truncate(text, max = 24_000) {
|
|
16
|
+
return text.length <= max ? text : `${text.slice(0, max)}\n…`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function apply(ctx) {
|
|
20
|
+
ctx.tools.register({
|
|
21
|
+
name: "plan",
|
|
22
|
+
description:
|
|
23
|
+
"Project plan in .dsh/PLAN.md. Create/read/update a plan, then approve it ONLY after the user explicitly agrees. While a plan is draft, do not edit code: propose steps and wait for approval.",
|
|
24
|
+
parameters: {
|
|
25
|
+
type: "object",
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
properties: {
|
|
28
|
+
action: { type: "string", enum: ["status", "read", "create", "update", "approve"], description: "status | read | create | update | approve" },
|
|
29
|
+
content: { type: "string", description: "Plan markdown for create/update." },
|
|
30
|
+
confirm: { type: "boolean", description: "Required for approve: true only after explicit user approval." },
|
|
31
|
+
force: { type: "boolean", description: "Overwrite an existing plan on create." },
|
|
32
|
+
},
|
|
33
|
+
required: ["action"],
|
|
34
|
+
},
|
|
35
|
+
timeoutMs: 15_000,
|
|
36
|
+
output: {
|
|
37
|
+
schema: { type: "object", additionalProperties: true },
|
|
38
|
+
render(_args, value) {
|
|
39
|
+
const text = typeof value?.text === "string" ? value.text : JSON.stringify(value);
|
|
40
|
+
return [{ type: "text", text: truncate(text) }];
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
async execute(args, exec) {
|
|
44
|
+
const cwd = cwdOf(exec);
|
|
45
|
+
const action = String(args?.action ?? "status");
|
|
46
|
+
if (action === "create") {
|
|
47
|
+
if (typeof args?.content !== "string" || args.content.trim().length === 0) return { ok: false, text: "plan create: нужен content" };
|
|
48
|
+
const plan = writePlan(cwd, args.content, { force: args?.force === true });
|
|
49
|
+
return { ok: true, plan, text: `План создан: ${plan.path}\nСтатус: draft. Жду подтверждения пользователя перед кодом.` };
|
|
50
|
+
}
|
|
51
|
+
if (action === "update") {
|
|
52
|
+
if (typeof args?.content !== "string" || args.content.trim().length === 0) return { ok: false, text: "plan update: нужен content" };
|
|
53
|
+
const plan = updatePlan(cwd, args.content);
|
|
54
|
+
return { ok: true, plan, text: `План обновлён: ${plan.path}` };
|
|
55
|
+
}
|
|
56
|
+
if (action === "approve") {
|
|
57
|
+
try {
|
|
58
|
+
const plan = approvePlan(cwd, { confirm: args?.confirm === true });
|
|
59
|
+
return { ok: true, plan, text: `План одобрен: ${plan.path}` };
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return { ok: false, text: String(error.message ?? error) };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const plan = readPlan(cwd);
|
|
65
|
+
if (!plan.exists) return { ok: true, exists: false, text: "PLAN.md нет. Создайте plan action=create content=..." };
|
|
66
|
+
if (action === "read") return { ok: true, ...plan, text: plan.body };
|
|
67
|
+
return {
|
|
68
|
+
ok: true,
|
|
69
|
+
exists: true,
|
|
70
|
+
status: plan.status,
|
|
71
|
+
path: plan.path,
|
|
72
|
+
createdAt: plan.createdAt,
|
|
73
|
+
updatedAt: plan.updatedAt,
|
|
74
|
+
approvedAt: plan.approvedAt,
|
|
75
|
+
text: `План: ${plan.path}\nСтатус: ${plan.status}\nОбновлён: ${plan.updatedAt ?? "—"}`,
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
ctx.systemPrompt.context({
|
|
81
|
+
name: "dsh-desktop-kit:plan-mode",
|
|
82
|
+
order: 133,
|
|
83
|
+
interpolate: false,
|
|
84
|
+
required: false,
|
|
85
|
+
text: (assemblyContext) => {
|
|
86
|
+
try {
|
|
87
|
+
const session = assemblyContext?.agent?.session;
|
|
88
|
+
const cwd = session?.header?.cwd;
|
|
89
|
+
if (typeof cwd !== "string" || cwd.length === 0) return "";
|
|
90
|
+
const plan = readPlan(cwd);
|
|
91
|
+
if (!plan.exists) return "";
|
|
92
|
+
const body = plan.body.length <= 1600 ? plan.body : `${plan.body.slice(0, 1600)}\n…`;
|
|
93
|
+
if (plan.status === "approved") {
|
|
94
|
+
return `<plan-mode status="approved">\nПлан одобрен: ${plan.path}. Следуйте шагам, отмечайте прогресс, не расширяйте scope без согласования.\n\n${body}\n</plan-mode>`;
|
|
95
|
+
}
|
|
96
|
+
return `<plan-mode status="draft">\nПлан в ${plan.path} ещё НЕ одобрен. Не редактируйте код и не запускайте изменяющие команды: уточните требования, обновите план и попросите пользователя одобрить (plan action=approve confirm=true только после явного согласия).\n\n${body}\n</plan-mode>`;
|
|
97
|
+
} catch {
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
ctx.logger?.info?.("[plan-mode] tool + prompt gate ready");
|
|
103
|
+
}
|