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.
Files changed (130) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +114 -27
  3. package/client.js +3270 -32
  4. package/docs/AGENT-HARNESS.md +144 -0
  5. package/docs/ARCHITECTURE.md +38 -7
  6. package/docs/COMPATIBILITY.md +4 -2
  7. package/docs/DEVELOPMENT.md +56 -0
  8. package/docs/INSTALL.md +20 -11
  9. package/docs/LAPTOP-INSTALL.md +96 -0
  10. package/docs/ROADMAP-3.0.md +353 -0
  11. package/docs/ROADMAP.md +1 -1
  12. package/docs/SUBAGENTS.md +25 -0
  13. package/docs/TEST-0.2.2.md +5 -5
  14. package/docs/TEST-2.3.0.md +49 -0
  15. package/docs/TEST-2.7.2.md +46 -0
  16. package/docs/TEST-2.7.3.md +20 -0
  17. package/docs/TOKENS.md +2 -2
  18. package/docs/TROUBLESHOOTING.md +27 -0
  19. package/docs/UI-QA.md +28 -0
  20. package/docs/images/peak-toast-peak.png +0 -0
  21. package/docs/images/peak-toast-soon.png +0 -0
  22. package/docs/preview/peak-toast.html +155 -0
  23. package/index.js +18 -1
  24. package/package.json +26 -6
  25. package/plugins/dsh-agent-extensions/README.md +107 -0
  26. package/plugins/dsh-agent-extensions/client.js +157 -0
  27. package/plugins/dsh-agent-extensions/index.js +174 -0
  28. package/plugins/dsh-agent-extensions/lib/agents.js +76 -0
  29. package/plugins/dsh-agent-extensions/lib/api.js +53 -0
  30. package/plugins/dsh-agent-extensions/lib/checkpoints.js +103 -0
  31. package/plugins/dsh-agent-extensions/lib/commands.js +255 -0
  32. package/plugins/dsh-agent-extensions/lib/hooks.js +182 -0
  33. package/plugins/dsh-agent-extensions/lib/policy.js +84 -0
  34. package/plugins/dsh-agent-extensions/lib/project.js +144 -0
  35. package/plugins/dsh-agent-extensions/lib/scaffold.js +151 -0
  36. package/plugins/dsh-agent-extensions/package.json +20 -0
  37. package/plugins/dsh-agent-extensions/screenshots/agents-1440.png +0 -0
  38. package/plugins/dsh-agent-extensions/screenshots/agents-390.png +0 -0
  39. package/plugins/dsh-browser-qa/README.md +221 -0
  40. package/plugins/dsh-browser-qa/cordis.patch.yml +4 -0
  41. package/plugins/dsh-browser-qa/index.js +165 -0
  42. package/plugins/dsh-browser-qa/lib/cdp.js +224 -0
  43. package/plugins/dsh-browser-qa/lib/chrome.js +466 -0
  44. package/plugins/dsh-browser-qa/lib/pages.js +552 -0
  45. package/plugins/dsh-browser-qa/lib/qa.js +827 -0
  46. package/plugins/dsh-browser-qa/lib/ui-gate.js +256 -0
  47. package/plugins/dsh-browser-qa/package.json +31 -0
  48. package/plugins/dsh-checkpoints/README.md +22 -0
  49. package/plugins/dsh-checkpoints/cordis.patch.yml +3 -0
  50. package/plugins/dsh-checkpoints/index.js +80 -0
  51. package/plugins/dsh-checkpoints/lib/checkpoints.js +111 -0
  52. package/plugins/dsh-checkpoints/package.json +25 -0
  53. package/plugins/dsh-github-review/README.md +131 -0
  54. package/plugins/dsh-github-review/cordis.patch.yml +3 -0
  55. package/plugins/dsh-github-review/index.js +536 -0
  56. package/plugins/dsh-github-review/lib/gh.js +494 -0
  57. package/plugins/dsh-github-review/lib/normalize.js +662 -0
  58. package/plugins/dsh-github-review/package.json +28 -0
  59. package/plugins/dsh-guardrails/README.md +264 -0
  60. package/plugins/dsh-guardrails/cordis.patch.yml +5 -0
  61. package/plugins/dsh-guardrails/index.js +389 -0
  62. package/plugins/dsh-guardrails/lib/hooks.js +262 -0
  63. package/plugins/dsh-guardrails/lib/modes.js +77 -0
  64. package/plugins/dsh-guardrails/lib/policy.js +592 -0
  65. package/plugins/dsh-guardrails/lib/scan.js +451 -0
  66. package/plugins/dsh-guardrails/lib/secrets.js +274 -0
  67. package/plugins/dsh-guardrails/package.json +26 -0
  68. package/plugins/dsh-mcp-manager/README.md +78 -0
  69. package/plugins/dsh-mcp-manager/client.js +373 -0
  70. package/plugins/dsh-mcp-manager/index.js +159 -0
  71. package/plugins/dsh-mcp-manager/lib/api.js +250 -0
  72. package/plugins/dsh-mcp-manager/lib/client.js +219 -0
  73. package/plugins/dsh-mcp-manager/lib/config.js +217 -0
  74. package/plugins/dsh-mcp-manager/lib/manager.js +146 -0
  75. package/plugins/dsh-mcp-manager/package.json +18 -0
  76. package/plugins/dsh-mcp-manager/screenshots/modal-390.png +0 -0
  77. package/plugins/dsh-mcp-manager/screenshots/settings-1440.png +0 -0
  78. package/plugins/dsh-mcp-manager/screenshots/settings-390.png +0 -0
  79. package/plugins/dsh-memory-bridge/README.md +161 -0
  80. package/plugins/dsh-memory-bridge/client.js +2264 -0
  81. package/plugins/dsh-memory-bridge/cordis.patch.yml +5 -0
  82. package/plugins/dsh-memory-bridge/docs/images/conflicts.png +0 -0
  83. package/plugins/dsh-memory-bridge/docs/images/hero.png +0 -0
  84. package/plugins/dsh-memory-bridge/docs/images/settings-dark.png +0 -0
  85. package/plugins/dsh-memory-bridge/docs/images/settings-light.png +0 -0
  86. package/plugins/dsh-memory-bridge/docs/images/settings-mobile.png +0 -0
  87. package/plugins/dsh-memory-bridge/docs/images/setup.png +0 -0
  88. package/plugins/dsh-memory-bridge/icon.svg +13 -0
  89. package/plugins/dsh-memory-bridge/index.js +378 -0
  90. package/plugins/dsh-memory-bridge/lib/bridge.js +636 -0
  91. package/plugins/dsh-memory-bridge/lib/github.js +473 -0
  92. package/plugins/dsh-memory-bridge/lib/merge.js +205 -0
  93. package/plugins/dsh-memory-bridge/lib/portable.js +314 -0
  94. package/plugins/dsh-memory-bridge/lib/state.js +565 -0
  95. package/plugins/dsh-memory-bridge/lib/sync.js +984 -0
  96. package/plugins/dsh-memory-bridge/locale/en.json +6 -0
  97. package/plugins/dsh-memory-bridge/locale/ru.json +6 -0
  98. package/plugins/dsh-memory-bridge/package.json +67 -0
  99. package/plugins/dsh-peak-guard/README.md +16 -3
  100. package/plugins/dsh-peak-guard/client.js +223 -8
  101. package/plugins/dsh-peak-guard/package.json +1 -1
  102. package/plugins/dsh-plan-mode/README.md +24 -0
  103. package/plugins/dsh-plan-mode/cordis.patch.yml +3 -0
  104. package/plugins/dsh-plan-mode/index.js +103 -0
  105. package/plugins/dsh-plan-mode/lib/plan.js +89 -0
  106. package/plugins/dsh-plan-mode/package.json +18 -0
  107. package/plugins/dsh-profile-transfer/client.js +277 -17
  108. package/plugins/dsh-profile-transfer/index.js +10 -0
  109. package/plugins/dsh-profile-transfer/package.json +3 -1
  110. package/plugins/dsh-profile-transfer/transfer.js +101 -2
  111. package/plugins/dsh-project-memory/index.js +92 -2
  112. package/plugins/dsh-project-memory/package.json +1 -1
  113. package/plugins/dsh-repo-map/README.md +26 -0
  114. package/plugins/dsh-repo-map/cordis.patch.yml +3 -0
  115. package/plugins/dsh-repo-map/index.js +99 -0
  116. package/plugins/dsh-repo-map/lib/repo-map.js +170 -0
  117. package/plugins/dsh-repo-map/package.json +25 -0
  118. package/plugins/dsh-skill-autopilot/index.js +135 -11
  119. package/skills/delivery-gate/SKILL.md +31 -0
  120. package/skills/git-and-ci/SKILL.md +5 -1
  121. package/skills/manifest.json +31 -6
  122. package/skills/requirements-and-planning/SKILL.md +3 -0
  123. package/skills/research-first/SKILL.md +35 -0
  124. package/skills/research-first/references/query-patterns.md +20 -0
  125. package/skills/subagent-playbook/SKILL.md +36 -0
  126. package/skills/subagent-playbook/references/prompts.md +39 -0
  127. package/skills/ui-craft/SKILL.md +43 -0
  128. package/skills/visual-qa/SKILL.md +29 -0
  129. package/skills/visual-qa/references/commands.md +32 -0
  130. 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 are off-peak too; only regular weekdays are shown.",
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: "Государственные праздники Китая тоже считаются off-peak — выше только обычные будни.",
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 < 8; index += 1) {
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.2.0",
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,3 @@
1
+ - insert:
2
+ - id: dsh-plan-mode
3
+ name: dsh-plan-mode
@@ -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
+ }