dsh-desktop-kit 2.7.7 → 2.7.9

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.7.9 (2026-11-12)
4
+
5
+ - **Worktrees.** `project_worktree` (`list|create|remove`) создаёт `.dsh/worktrees/<slug>` на ветке `dsh/<slug>`, пишет путь в `.git/info/exclude`, отказывает в удалении грязного worktree без `force`.
6
+ - **Фоновые агенты.** `project_agent` получил `background=true`, `worktree=auto|<slug>`, `status` и `cancel`; реестр `.dsh/agents/runs.json` хранит id, ветку, статус и короткий результат.
7
+ - **Agents UI.** Секции Worktrees и «Фоновые запуски», кнопки «Обновить» и «Остановить»; адаптив 390/768/1440 без overflow/markup.
8
+ - **Тесты.** `worktrees-tests`, `background-agents-tests`, полный `npm test`.
9
+
10
+ ## 2.7.8 (2026-11-12)
11
+
12
+ - **Permissions UI.** Настройки → **Права**: эффективный preset DSH (`current`), sandbox/approval, каталог пресетов, жёсткие правила kit и защищённые пути. Страница read-only, без второго picker и без хранения approvals.
13
+ - **Guardrails RPC.** `/api/dsh-guardrails/permissions`: `catalog/current/resolve/optionOf` + снимок политики; привязка к сессии и workspace.
14
+ - **UI.** 390/768/1440: overflow 0, console 0, markup 0; скриншоты 1440/390.
15
+ - **2.7.7** (staged) содержала MCP prompts; 2.7.8 включает и prompts, и Permissions UI.
16
+
3
17
  ## 2.7.7 (2026-11-12)
4
18
 
5
19
  - **MCP prompts.** Settings → MCP → **Prompts**: список промптов сервера, аргументы, запуск, результат и копирование. Новые RPC `prompts`/`prompt`; клиент поддерживает `prompts/list` и `prompts/get` с пагинацией.
package/README.md CHANGED
@@ -4,17 +4,17 @@
4
4
 
5
5
  Набор из **16 плагинов и 30 навыков** для DeepSeek Harness 0.2.x: русский интерфейс, автопилот навыков, токен-лаборатория, студия навыков, память проектов, обмен памятью между машинами (GitHub и USB), guardrails, карта репозитория, чекпоинты, plan mode, browser QA, GitHub review, Claude Code-подобные hooks/agents/commands, перенос чатов и резервные копии профиля.
6
6
 
7
- ![DSH Desktop Kit 2.7](https://unpkg.com/dsh-desktop-kit@2.7.4/docs/images/hero.png)
7
+ ![DSH Desktop Kit 2.7](https://unpkg.com/dsh-desktop-kit@2.7.9/docs/images/hero.png)
8
8
 
9
9
  ## MCP: подключения без JSON
10
10
 
11
11
  Настройки → **MCP**: пресеты GitHub, PostgreSQL и Filesystem, живые статусы, health, список tools и безопасное хранение токенов в `~/.dsh/mcp.secrets.json`. Промпты сервера открываются кнопкой **Prompts**: аргументы, запуск и копирование результата.
12
12
 
13
- ![MCP Settings](https://unpkg.com/dsh-desktop-kit@2.7.4/plugins/dsh-mcp-manager/screenshots/settings-1440.png)
13
+ ![MCP Settings](https://unpkg.com/dsh-desktop-kit@2.7.9/plugins/dsh-mcp-manager/screenshots/settings-1440.png)
14
14
 
15
- ![MCP на 390px](https://unpkg.com/dsh-desktop-kit@2.7.4/plugins/dsh-mcp-manager/screenshots/settings-390.png)
15
+ ![MCP на 390px](https://unpkg.com/dsh-desktop-kit@2.7.9/plugins/dsh-mcp-manager/screenshots/settings-390.png)
16
16
 
17
- ![Добавление MCP-сервера](https://unpkg.com/dsh-desktop-kit@2.7.4/plugins/dsh-mcp-manager/screenshots/modal-390.png)
17
+ ![Добавление MCP-сервера](https://unpkg.com/dsh-desktop-kit@2.7.9/plugins/dsh-mcp-manager/screenshots/modal-390.png)
18
18
 
19
19
  ## Права и субагенты
20
20
 
@@ -22,10 +22,13 @@
22
22
  - **`.dsh/agents/*.md`** реально ограничивает `tools`, `permission` и `model` ребёнка; reviewer не может править, researcher — писать.
23
23
  - **Approval** — штатная карточка DSH: Allow once / Always allow / Reject.
24
24
  - **`.dsh/`** создаётся автоматически: agents, commands, rules, hooks, verify, mcp, PROJECT.md.
25
+ - **Worktrees и фоновые агенты.** `project_worktree` создаёт ветку `dsh/<slug>` и отдельную папку `.dsh/worktrees/<slug>`; `project_agent ... worktree=auto background=true` запускает агента в изоляции, а страница **Агенты** показывает worktrees и очередь запусков с кнопкой «Остановить».
25
26
 
26
- ![Агенты проекта](https://unpkg.com/dsh-desktop-kit@2.7.4/plugins/dsh-agent-extensions/screenshots/agents-1440.png)
27
+ ![Агенты проекта](https://unpkg.com/dsh-desktop-kit@2.7.9/plugins/dsh-agent-extensions/screenshots/agents-1440.png)
27
28
 
28
- **v2.7.7** · MIT · проверено на DSH `0.2.1-rc.5` и `0.2.2-rc.1` (Windows 11) · без внешних зависимостей и телеметрии
29
+ ![Права и ограничения](https://unpkg.com/dsh-desktop-kit@2.7.9/plugins/dsh-guardrails/screenshots/permissions-1440.png)
30
+
31
+ **v2.7.9** · MIT · проверено на DSH `0.2.1-rc.5` и `0.2.2-rc.1` (Windows 11) · без внешних зависимостей и телеметрии
29
32
 
30
33
  **План развития:** [docs/ROADMAP-3.0.md](docs/ROADMAP-3.0.md) — Memory Fabric, семантическое
31
34
  слияние, локальный индекс памяти, шифрованные переносы, Memory Hub и npm trusted publishing.
@@ -113,27 +116,27 @@
113
116
  - перед каждой синхронизацией проверяется, что репозиторий всё ещё приватный;
114
117
  - секреты и PII вычищаются, домашние пути заменяются токеном `%DSH_HOME%`; LLM не вызывается — **0 токенов**.
115
118
 
116
- ![Настройки памяти проектов](https://unpkg.com/dsh-desktop-kit@2.7.4/plugins/dsh-memory-bridge/docs/images/settings-light.png)
119
+ ![Настройки памяти проектов](https://unpkg.com/dsh-desktop-kit@2.7.9/plugins/dsh-memory-bridge/docs/images/settings-light.png)
117
120
 
118
- ![Конфликты досье](https://unpkg.com/dsh-desktop-kit@2.7.4/plugins/dsh-memory-bridge/docs/images/conflicts.png)
121
+ ![Конфликты досье](https://unpkg.com/dsh-desktop-kit@2.7.9/plugins/dsh-memory-bridge/docs/images/conflicts.png)
119
122
 
120
123
  ## Скриншоты
121
124
 
122
125
  ### Skills Studio: каталог, стоимость и редактор
123
126
 
124
- ![Каталог навыков](https://unpkg.com/dsh-desktop-kit@2.7.4/plugins/dsh-gkb-ru-ui/screenshots/02-skills-1440-light.png)
127
+ ![Каталог навыков](https://unpkg.com/dsh-desktop-kit@2.7.9/plugins/dsh-gkb-ru-ui/screenshots/02-skills-1440-light.png)
125
128
 
126
- ![Редактор навыка](https://unpkg.com/dsh-desktop-kit@2.7.4/plugins/dsh-gkb-ru-ui/screenshots/03-skills-editor-1440-light.png)
129
+ ![Редактор навыка](https://unpkg.com/dsh-desktop-kit@2.7.9/plugins/dsh-gkb-ru-ui/screenshots/03-skills-editor-1440-light.png)
127
130
 
128
131
  ### Token Lab: куда уходит контекст
129
132
 
130
- ![Токен-лаборатория](https://unpkg.com/dsh-desktop-kit@2.7.4/plugins/dsh-token-lab/screenshots/01-token-lab-1440-light.png)
133
+ ![Токен-лаборатория](https://unpkg.com/dsh-desktop-kit@2.7.9/plugins/dsh-token-lab/screenshots/01-token-lab-1440-light.png)
131
134
 
132
135
  ### Тёмная тема и мобильный
133
136
 
134
- ![Тёмная тема](https://unpkg.com/dsh-desktop-kit@2.7.4/plugins/dsh-gkb-ru-ui/screenshots/04-skills-1440-dark.png)
137
+ ![Тёмная тема](https://unpkg.com/dsh-desktop-kit@2.7.9/plugins/dsh-gkb-ru-ui/screenshots/04-skills-1440-dark.png)
135
138
 
136
- ![Каталог навыков на 390 px](https://unpkg.com/dsh-desktop-kit@2.7.4/plugins/dsh-gkb-ru-ui/screenshots/05-skills-390-light.png)
139
+ ![Каталог навыков на 390 px](https://unpkg.com/dsh-desktop-kit@2.7.9/plugins/dsh-gkb-ru-ui/screenshots/05-skills-390-light.png)
137
140
 
138
141
  ## Окно-напоминание: пиковый тариф
139
142
 
@@ -143,11 +146,11 @@ DeepSeek включает двойной тариф в пиковые часы (
143
146
  - **в окне** — красная карточка: причина, границы окна в местном времени и UTC, живой отсчёт до конца и рекомендация, когда вернуться;
144
147
  - **после окна** — карточка исчезает; состояние всегда доступно через `window.__DSH_PEAK_GUARD__.state()`.
145
148
 
146
- ![Окно-напоминание: светлая тема](https://unpkg.com/dsh-desktop-kit@2.7.4/plugins/dsh-peak-guard/screenshots/03-peak-card-1440-light.png)
149
+ ![Окно-напоминание: светлая тема](https://unpkg.com/dsh-desktop-kit@2.7.9/plugins/dsh-peak-guard/screenshots/03-peak-card-1440-light.png)
147
150
 
148
- ![Окно-напоминание: тёмная тема](https://unpkg.com/dsh-desktop-kit@2.7.4/plugins/dsh-peak-guard/screenshots/04-peak-card-1440-dark.png)
151
+ ![Окно-напоминание: тёмная тема](https://unpkg.com/dsh-desktop-kit@2.7.9/plugins/dsh-peak-guard/screenshots/04-peak-card-1440-dark.png)
149
152
 
150
- ![Окно-напоминание на 390 px](https://unpkg.com/dsh-desktop-kit@2.7.4/plugins/dsh-peak-guard/screenshots/05-peak-card-390-light.png)
153
+ ![Окно-напоминание на 390 px](https://unpkg.com/dsh-desktop-kit@2.7.9/plugins/dsh-peak-guard/screenshots/05-peak-card-390-light.png)
151
154
 
152
155
  Подробности и настройки — [plugins/dsh-peak-guard/README.md](plugins/dsh-peak-guard/README.md).
153
156
 
package/client.js CHANGED
@@ -7411,7 +7411,25 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
7411
7411
  .ag-error{padding:8px 10px;border:1px solid rgba(159,18,57,.16);border-radius:9px;background:rgba(159,18,57,.06);color:#9f1239;font-size:12px;line-height:1.4}
7412
7412
  .ag-loading{color:var(--ag-muted);font-size:13px;padding:18px 0}
7413
7413
  .ag-note{margin:0;color:var(--ag-muted);font-size:11px;line-height:1.45}
7414
- @media(max-width:860px){.ag-grid{grid-template-columns:1fr}.ag-stats{grid-template-columns:1fr 1fr}}
7414
+ .ag-btn{font:inherit;font-size:11px;font-weight:650;border:1px solid var(--ag-line);background:var(--ag-panel);color:var(--ag-text);border-radius:8px;padding:6px 9px;cursor:pointer;min-height:30px}
7415
+ .ag-btn:hover{border-color:rgba(15,118,110,.35);background:rgba(15,118,110,.05)}
7416
+ .ag-btn:focus-visible{outline:2px solid var(--ag-accent);outline-offset:2px}
7417
+ .ag-section{display:flex;flex-direction:column;gap:8px;min-width:0}
7418
+ .ag-section h3{margin:0;font-size:15px;letter-spacing:-.01em}
7419
+ .ag-hint{margin:0;color:var(--ag-muted);font-size:11.5px;line-height:1.45}
7420
+ .ag-items{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;min-width:0}
7421
+ .ag-item{border:1px solid var(--ag-line);border-radius:12px;background:var(--ag-panel);padding:11px 12px;min-width:0;display:flex;flex-direction:column;gap:5px}
7422
+ .ag-item-top{display:flex;justify-content:space-between;gap:8px;align-items:baseline;min-width:0}
7423
+ .ag-item-top code{font:inherit;font-weight:650;font-size:12px;color:var(--ag-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
7424
+ .ag-item-top span{flex:none;font-size:9.5px;font-weight:750;letter-spacing:.06em;text-transform:uppercase;color:var(--ag-muted)}
7425
+ .ag-item p{margin:0;color:var(--ag-muted);font-size:11px;line-height:1.4;overflow-wrap:anywhere}
7426
+ .ag-item p.ag-task{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
7427
+ .ag-status-running{color:#9a3412;border-color:rgba(154,52,18,.22);background:rgba(154,52,18,.06)}
7428
+ .ag-status-completed{color:#0f766e;border-color:rgba(15,118,110,.22);background:rgba(15,118,110,.06)}
7429
+ .ag-status-failed,.ag-status-canceled{color:#9f1239;border-color:rgba(159,18,57,.18);background:rgba(159,18,57,.05)}
7430
+ .ag-mini-empty{padding:12px;border:1px dashed var(--ag-line);border-radius:10px;color:var(--ag-muted);font-size:11.5px;line-height:1.45;background:linear-gradient(160deg,var(--ag-soft),transparent 80%)}
7431
+ .ag-actions{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:2px}
7432
+ @media(max-width:860px){.ag-grid{grid-template-columns:1fr}.ag-stats{grid-template-columns:1fr 1fr}.ag-items{grid-template-columns:1fr}}
7415
7433
  @media(max-width:520px){.ag-stats{grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.ag-stat{padding:10px 12px}.ag-stat b{font-size:20px}.ag-stat span{font-size:9px;letter-spacing:.04em}.ag-root{gap:16px}}
7416
7434
  @media(prefers-reduced-motion:reduce){.ag-card{transition:none}}
7417
7435
  `;
@@ -7459,6 +7477,7 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
7459
7477
 
7460
7478
  function AgentsSection(props) {
7461
7479
  const [state, setState] = useState({ loading: true, error: null, data: null });
7480
+ const [canceling, setCanceling] = useState("");
7462
7481
  const useSessions = props && props.useSessions;
7463
7482
  const useWorkspaces = props && props.useWorkspaces;
7464
7483
  const sessionId = useSessions ? useSessions(currentSessionSelector) : undefined;
@@ -7468,6 +7487,16 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
7468
7487
  ?? workspaces.find((item) => Array.isArray(item.sessionIds) && item.sessionIds.length > 0)
7469
7488
  ?? workspaces[0];
7470
7489
  const cwd = typeof workspace?.path === "string" ? workspace.path : undefined;
7490
+
7491
+ const load = async () => {
7492
+ try {
7493
+ const data = await call(window.__DSH_AGENTS_CTX__, "list", { sessionId, cwd });
7494
+ setState({ loading: false, error: null, data });
7495
+ } catch (error) {
7496
+ setState({ loading: false, error: fallbackError(error), data: null });
7497
+ }
7498
+ };
7499
+
7471
7500
  useEffect(() => {
7472
7501
  let cancelled = false;
7473
7502
  (async () => {
@@ -7480,7 +7509,22 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
7480
7509
  })();
7481
7510
  return () => { cancelled = true; };
7482
7511
  }, [sessionId, cwd]);
7512
+
7513
+ const cancel = async (id) => {
7514
+ setCanceling(id);
7515
+ try {
7516
+ await call(window.__DSH_AGENTS_CTX__, "cancel", { sessionId, cwd, id });
7517
+ await load();
7518
+ } catch (error) {
7519
+ setState((current) => ({ ...current, error: fallbackError(error) }));
7520
+ } finally {
7521
+ setCanceling("");
7522
+ }
7523
+ };
7524
+
7483
7525
  const agents = state.data?.agents ?? [];
7526
+ const worktrees = state.data?.worktrees ?? [];
7527
+ const runs = state.data?.runs ?? [];
7484
7528
  const readOnly = agents.filter((agent) => agent.permission === "read-only").length;
7485
7529
  const models = new Set(agents.map((agent) => agent.model).filter(Boolean)).size;
7486
7530
  return h("section", { className: "ag-root", "aria-label": "Агенты проекта" },
@@ -7488,8 +7532,9 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
7488
7532
  h("div", null,
7489
7533
  h("p", { className: "ag-eyebrow" }, "Agents · project roles"),
7490
7534
  h("h2", { className: "ag-title" }, "Агенты"),
7491
- h("p", { className: "ag-sub" }, "Проектные роли из `.dsh/agents/*.md`. Их `tools`, `permission` и `model` применяются к дочернему агенту при запуске `project_agent`."),
7535
+ h("p", { className: "ag-sub" }, "Проектные роли из `.dsh/agents/*.md`. Их `tools`, `permission` и `model` применяются к дочернему агенту при запуске `project_agent`. Фоновые запуски и worktrees — здесь же."),
7492
7536
  ),
7537
+ h("button", { className: "ag-btn", type: "button", onClick: () => void load() }, "Обновить"),
7493
7538
  ),
7494
7539
  state.error ? h("div", { className: "ag-error", role: "alert" }, state.error) : null,
7495
7540
  h("div", { className: "ag-stats" },
@@ -7504,6 +7549,38 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
7504
7549
  h("pre", { className: "ag-code" }, "---\nname: reviewer\ndescription: Ревью диффа\nmodel: deepseek-flash\ntools: read, grep\npermission: read-only\n---\n\nТы ревьюер. Верни только findings."),
7505
7550
  ) : null,
7506
7551
  agents.length > 0 ? h("div", { className: "ag-grid" }, agents.map((agent) => h(AgentCard, { key: agent.name, agent }))) : null,
7552
+ h("div", { className: "ag-section" },
7553
+ h("h3", null, "Worktrees"),
7554
+ h("p", { className: "ag-hint" }, "Изолированные рабочие копии: `project_worktree create` или `project_agent` с `worktree=auto`."),
7555
+ state.data?.worktreeError ? h("div", { className: "ag-error", role: "alert" }, state.data.worktreeError) : null,
7556
+ worktrees.length > 0 ? h("div", { className: "ag-items" }, worktrees.map((tree) => h("article", { className: "ag-item", key: tree.id },
7557
+ h("div", { className: "ag-item-top" },
7558
+ h("code", { title: tree.branch || tree.id }, tree.branch || tree.id),
7559
+ h("span", { className: tree.dirty ? "ag-status-running" : "ag-status-completed" }, tree.dirty ? "dirty" : "clean"),
7560
+ ),
7561
+ h("p", { title: tree.path }, tree.path),
7562
+ ))) : h("div", { className: "ag-mini-empty" }, "Worktree пока нет. Создайте через инструмент `project_worktree`."),
7563
+ ),
7564
+ h("div", { className: "ag-section" },
7565
+ h("h3", null, "Фоновые запуски"),
7566
+ h("p", { className: "ag-hint" }, "`project_agent` с `background=true` возвращает id сразу; статус сохраняется в `.dsh/agents/runs.json`."),
7567
+ runs.length > 0 ? h("div", { className: "ag-items" }, runs.slice().reverse().map((run) => h("article", { className: "ag-item", key: run.id },
7568
+ h("div", { className: "ag-item-top" },
7569
+ h("code", { title: run.id }, run.name),
7570
+ h("span", { className: `ag-status-${run.status}` }, run.status),
7571
+ ),
7572
+ run.task ? h("p", { className: "ag-task", title: run.task }, run.task) : null,
7573
+ h("div", { className: "ag-actions" },
7574
+ run.worktree?.branch ? h("p", null, run.worktree.branch) : null,
7575
+ run.status === "running" ? h("button", {
7576
+ className: "ag-btn",
7577
+ type: "button",
7578
+ disabled: canceling === run.id,
7579
+ onClick: () => void cancel(run.id),
7580
+ }, canceling === run.id ? "Останавливаем…" : "Остановить") : null,
7581
+ ),
7582
+ ))) : h("div", { className: "ag-mini-empty" }, "Фоновых запусков нет."),
7583
+ ),
7507
7584
  h("p", { className: "ag-note" }, state.data?.project ? `Проект: ${state.data.project}` : "Проект определяется по текущей рабочей папке."),
7508
7585
  );
7509
7586
  }
@@ -7530,7 +7607,220 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
7530
7607
  return module.exports;
7531
7608
  })());
7532
7609
 
7533
- const __partNames = ["dsh-gkb-ru-ui","dsh-token-lab","dsh-peak-guard","dsh-session-move","dsh-profile-transfer","dsh-memory-bridge","dsh-mcp-manager","dsh-agent-extensions"];
7610
+ /* ---- dsh-guardrails ---- */
7611
+ __parts.push((function () {
7612
+ var module = { exports: {} };
7613
+ var exports = module.exports;
7614
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7615
+ const React = require("react");
7616
+ const h = React.createElement;
7617
+ const useEffect = React.useEffect;
7618
+ const useState = React.useState;
7619
+
7620
+ const CSS = `
7621
+ .pr-root{--pr-line:var(--dsw-alias-border-l1,rgba(20,40,36,.13));--pr-soft:var(--dsw-alias-bg-layer-1,#f4f7f5);--pr-panel:var(--dsw-alias-bg-layer-2,#fff);--pr-text:var(--dsw-alias-label-primary,#17231f);--pr-muted:var(--dsw-alias-label-secondary,#61706b);--pr-accent:var(--dsw-alias-brand-primary,#0f766e);display:flex;flex-direction:column;gap:20px;max-width:1040px;min-width:0;color:var(--pr-text)}
7622
+ .pr-head{display:flex;align-items:flex-end;justify-content:space-between;gap:20px;flex-wrap:wrap}
7623
+ .pr-eyebrow{margin:0 0 6px;font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--pr-accent)}
7624
+ .pr-title{margin:0;font-size:clamp(24px,2.6vw,32px);line-height:1.1;letter-spacing:-.025em;font-weight:680}
7625
+ .pr-sub{margin:8px 0 0;max-width:68ch;color:var(--pr-muted);font-size:14px;line-height:1.5}
7626
+ .pr-mode{padding:20px;border:1px solid var(--pr-line);border-radius:18px;background:linear-gradient(145deg,var(--pr-panel),var(--pr-soft));min-width:0}
7627
+ .pr-mode-top{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap}
7628
+ .pr-mode h3{margin:0;font-size:24px;line-height:1.1;letter-spacing:-.03em}
7629
+ .pr-chips{display:flex;gap:6px;flex-wrap:wrap}
7630
+ .pr-chip{padding:3px 8px;border:1px solid var(--pr-line);border-radius:999px;background:var(--pr-panel);font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--pr-muted);white-space:nowrap}
7631
+ .pr-chip.sandbox{color:#0f766e;border-color:rgba(15,118,110,.25);background:rgba(15,118,110,.07)}
7632
+ .pr-chip.approval{color:#9a3412;border-color:rgba(154,52,18,.22);background:rgba(154,52,18,.06)}
7633
+ .pr-mode p{margin:8px 0 0;color:var(--pr-muted);font-size:13px;line-height:1.5;max-width:70ch}
7634
+ .pr-note{display:flex;gap:8px;align-items:flex-start;margin-top:14px;padding:10px 12px;border:1px solid rgba(15,118,110,.16);border-radius:10px;background:rgba(15,118,110,.05);color:var(--pr-muted);font-size:12px;line-height:1.45}
7635
+ .pr-note b{color:var(--pr-text);font-weight:650}
7636
+ .pr-stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
7637
+ .pr-stat{padding:14px 16px;border:1px solid var(--pr-line);border-radius:14px;background:var(--pr-panel);min-width:0}
7638
+ .pr-stat b{display:block;font-size:24px;line-height:1.1;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
7639
+ .pr-stat span{display:block;margin-top:5px;color:var(--pr-muted);font-size:11px;font-weight:650;letter-spacing:.07em;text-transform:uppercase}
7640
+ .pr-section{display:flex;flex-direction:column;gap:10px;min-width:0}
7641
+ .pr-section h3{margin:0;font-size:15px;letter-spacing:-.01em}
7642
+ .pr-section .pr-hint{margin:0;color:var(--pr-muted);font-size:12px;line-height:1.45}
7643
+ .pr-presets{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
7644
+ .pr-preset{padding:12px;border:1px solid var(--pr-line);border-radius:12px;background:var(--pr-panel);min-width:0}
7645
+ .pr-preset.active{border-color:rgba(15,118,110,.35);background:rgba(15,118,110,.06)}
7646
+ .pr-preset b{display:flex;justify-content:space-between;gap:8px;font-size:13px;align-items:center}
7647
+ .pr-preset span{display:block;margin-top:4px;color:var(--pr-muted);font-size:11.5px;line-height:1.4}
7648
+ .pr-preset em{font-style:normal;color:var(--pr-accent);font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}
7649
+ .pr-list{display:flex;flex-direction:column;border:1px solid var(--pr-line);border-radius:14px;overflow:hidden;background:var(--pr-panel)}
7650
+ .pr-row{display:flex;gap:10px;align-items:baseline;padding:9px 12px;border-top:1px solid var(--pr-line);font-size:12px;line-height:1.4;min-width:0}
7651
+ .pr-row:first-child{border-top:0}
7652
+ .pr-sev{flex:none;width:46px;font-size:9.5px;font-weight:750;letter-spacing:.08em;text-transform:uppercase}
7653
+ .pr-sev.deny{color:#9f1239}.pr-sev.ask{color:#9a3412}
7654
+ .pr-row code{flex:none;color:var(--pr-text);font:inherit;font-weight:650;white-space:nowrap}
7655
+ .pr-row span{color:var(--pr-muted);overflow:hidden;text-overflow:ellipsis;min-width:0}
7656
+ .pr-path{padding:9px 12px;border-top:1px solid var(--pr-line);font-size:12px;line-height:1.45;min-width:0}
7657
+ .pr-path:first-child{border-top:0}
7658
+ .pr-path b{display:block;font-size:12px}
7659
+ .pr-path code{display:block;margin-top:3px;color:var(--pr-muted);font:11.5px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;overflow-wrap:anywhere}
7660
+ .pr-error{padding:8px 10px;border:1px solid rgba(159,18,57,.16);border-radius:9px;background:rgba(159,18,57,.06);color:#9f1239;font-size:12px;line-height:1.4}
7661
+ .pr-loading{color:var(--pr-muted);font-size:13px;padding:18px 0}
7662
+ .pr-empty{padding:18px;border:1px dashed var(--pr-line);border-radius:14px;color:var(--pr-muted);font-size:12.5px;line-height:1.5;background:linear-gradient(160deg,var(--pr-soft),transparent 75%)}
7663
+ .pr-foot{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;color:var(--pr-muted);font-size:11px;line-height:1.4}
7664
+ @media(max-width:860px){.pr-presets{grid-template-columns:1fr 1fr}.pr-stats{grid-template-columns:1fr 1fr}}
7665
+ @media(max-width:520px){.pr-presets{grid-template-columns:1fr}.pr-stats{grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.pr-stat{padding:10px 12px}.pr-stat b{font-size:20px}.pr-stat span{font-size:9px;letter-spacing:.04em}.pr-root{gap:16px}.pr-mode{padding:16px}.pr-note{flex-direction:column;gap:4px}.pr-row{display:flex;flex-wrap:wrap;gap:4px 8px}.pr-row code{white-space:normal;overflow-wrap:anywhere}.pr-row span{flex-basis:100%;padding-left:0}}
7666
+ `;
7667
+
7668
+ const SEVERITY_ORDER = { deny: 0, ask: 1 };
7669
+
7670
+ function currentSessionSelector(state) {
7671
+ try {
7672
+ const sessions = Object.values(state.byId || {});
7673
+ const main = sessions.find((session) => session && session.retainedBy && (session.retainedBy.mainView || 0) > 0);
7674
+ if (main) return main.id;
7675
+ const ids = Array.isArray(state.ids) ? state.ids : [];
7676
+ return ids.length > 0 ? ids[ids.length - 1] : undefined;
7677
+ } catch { return undefined; }
7678
+ }
7679
+
7680
+ async function call(ctx, operation, payload) {
7681
+ let signal;
7682
+ try { signal = AbortSignal.timeout(30000); } catch { signal = undefined; }
7683
+ const result = await ctx.connection.rpc.call("/api", `dsh-guardrails/${operation}`, payload ?? {}, signal);
7684
+ if (!result || result.ok !== true) throw new Error(result?.error?.message || "Ошибка RPC");
7685
+ return result.value;
7686
+ }
7687
+
7688
+ function errorOf(value) { return value instanceof Error ? value.message : String(value); }
7689
+
7690
+ function SandboxChip({ value }) {
7691
+ const labels = {
7692
+ "read-only": "Read only",
7693
+ "workspace-write": "Workspace write",
7694
+ "danger-full-access": "Full access",
7695
+ };
7696
+ return h("span", { className: "pr-chip sandbox" }, labels[value] || value || "sandbox");
7697
+ }
7698
+
7699
+ function PermissionsSection(props) {
7700
+ const [state, setState] = useState({ loading: true, error: null, data: null });
7701
+ const useSessions = props && props.useSessions;
7702
+ const useWorkspaces = props && props.useWorkspaces;
7703
+ const sessionId = useSessions ? useSessions(currentSessionSelector) : undefined;
7704
+ const workspaceSnapshot = useWorkspaces ? useWorkspaces((snapshot) => snapshot) : undefined;
7705
+ const workspaces = Array.isArray(workspaceSnapshot?.items) ? workspaceSnapshot.items : [];
7706
+ const workspace = workspaces.find((item) => Array.isArray(item.sessionIds) && item.sessionIds.includes(sessionId))
7707
+ ?? workspaces.find((item) => Array.isArray(item.sessionIds) && item.sessionIds.length > 0)
7708
+ ?? workspaces[0];
7709
+ const cwd = typeof workspace?.path === "string" ? workspace.path : undefined;
7710
+
7711
+ useEffect(() => {
7712
+ let cancelled = false;
7713
+ (async () => {
7714
+ try {
7715
+ const data = await call(window.__DSH_GUARDRAILS_CTX__, "permissions", { sessionId, cwd });
7716
+ if (!cancelled) setState({ loading: false, error: null, data });
7717
+ } catch (error) {
7718
+ if (!cancelled) setState({ loading: false, error: errorOf(error), data: null });
7719
+ }
7720
+ })();
7721
+ return () => { cancelled = true; };
7722
+ }, [sessionId, cwd]);
7723
+
7724
+ const preset = state.data?.preset;
7725
+ const guard = state.data?.guard;
7726
+ const options = Array.isArray(preset?.catalog?.options) ? preset.catalog.options : [];
7727
+ const rules = Array.isArray(guard?.rules)
7728
+ ? [...guard.rules].sort((a, b) => (SEVERITY_ORDER[a.severity] ?? 9) - (SEVERITY_ORDER[b.severity] ?? 9))
7729
+ : [];
7730
+ const protectedPaths = Array.isArray(guard?.protectedPaths) ? guard.protectedPaths : [];
7731
+ const current = preset?.current || "custom";
7732
+ const spec = preset?.spec;
7733
+ const option = preset?.option;
7734
+ const title = option?.name || current;
7735
+
7736
+ return h("section", { className: "pr-root", "aria-label": "Права и ограничения" },
7737
+ h("div", { className: "pr-head" },
7738
+ h("div", null,
7739
+ h("p", { className: "pr-eyebrow" }, "Guardrails · effective permissions"),
7740
+ h("h2", { className: "pr-title" }, "Права"),
7741
+ h("p", { className: "pr-sub" }, "Что разрешено в этом чате и что kit запрещает в любом режиме. Режим переключается штатным picker DSH в композере — здесь только прозрачный срез."),
7742
+ ),
7743
+ ),
7744
+ state.error ? h("div", { className: "pr-error", role: "alert" }, state.error) : null,
7745
+ state.loading ? h("div", { className: "pr-loading" }, "Читаем эффективные права…") : null,
7746
+ !state.loading && state.data ? h(React.Fragment, null,
7747
+ h("div", { className: "pr-mode" },
7748
+ h("div", { className: "pr-mode-top" },
7749
+ h("div", null,
7750
+ h("h3", null, title),
7751
+ h("p", null, option?.description || spec?.description || "Эффективный режим этого чата."),
7752
+ ),
7753
+ h("div", { className: "pr-chips" },
7754
+ h(SandboxChip, { value: spec?.sandbox }),
7755
+ h("span", { className: "pr-chip approval" }, spec?.approval === "never" ? "approval never" : "approval ask"),
7756
+ ),
7757
+ ),
7758
+ h("div", { className: "pr-note" },
7759
+ h("b", null, "Не второй picker."),
7760
+ h("span", null, "Смена режима — только в DSH. Эта страница не меняет разрешения и не хранит approvals; каждая карточка подтверждения остаётся в чате."),
7761
+ ),
7762
+ ),
7763
+ h("div", { className: "pr-stats" },
7764
+ h("div", { className: "pr-stat" }, h("b", null, rules.length), h("span", null, "жёстких правил")),
7765
+ h("div", { className: "pr-stat" }, h("b", null, protectedPaths.length), h("span", null, "защищённых путей")),
7766
+ h("div", { className: "pr-stat" }, h("b", null, options.length), h("span", null, "пресетов DSH")),
7767
+ ),
7768
+ h("div", { className: "pr-section" },
7769
+ h("h3", null, "Пресеты DSH"),
7770
+ h("p", { className: "pr-hint" }, "Доступны в композере. Текущий выделен; kit не дублирует переключатель."),
7771
+ h("div", { className: "pr-presets" }, options.map((item) => h("div", { key: item.value, className: `pr-preset${item.value === current ? " active" : ""}` },
7772
+ h("b", null, h("span", null, item.name || item.value), item.value === current ? h("em", null, "сейчас") : null),
7773
+ h("span", null, item.description || "Без описания"),
7774
+ ))),
7775
+ ),
7776
+ h("div", { className: "pr-section" },
7777
+ h("h3", null, "Жёсткие правила kit"),
7778
+ h("p", { className: "pr-hint" }, "Действуют даже в Full access. Отключаются только политикой guardrails, не режимом DSH."),
7779
+ rules.length > 0 ? h("div", { className: "pr-list" }, rules.map((rule) => h("div", { className: "pr-row", key: rule.id },
7780
+ h("span", { className: `pr-sev ${rule.severity}` }, rule.severity),
7781
+ h("code", null, rule.id),
7782
+ h("span", { title: rule.description }, rule.description || ""),
7783
+ ))) : h("div", { className: "pr-empty" }, "Правила отключены политикой guardrails."),
7784
+ ),
7785
+ h("div", { className: "pr-section" },
7786
+ h("h3", null, "Защищённые пути"),
7787
+ h("p", { className: "pr-hint" }, "Запись, редактирование и удаление этих путей отклоняются до выполнения."),
7788
+ protectedPaths.length > 0 ? h("div", { className: "pr-list" }, protectedPaths.map((entry) => h("div", { className: "pr-path", key: entry.id },
7789
+ h("b", null, entry.id),
7790
+ h("code", null, `${entry.match} /${entry.pattern}/`),
7791
+ ))) : h("div", { className: "pr-empty" }, "Защищённые пути отключены."),
7792
+ ),
7793
+ ) : null,
7794
+ h("div", { className: "pr-foot" },
7795
+ h("span", null, guard?.policyPath ? `Политика: ${guard.policyPath}` : "Политика guardrails"),
7796
+ h("span", null, guard ? (guard.enabled ? "hooks активны" : "hooks отключены") : ""),
7797
+ ),
7798
+ );
7799
+ }
7800
+
7801
+ function apply(ctx) {
7802
+ window.__DSH_GUARDRAILS_CTX__ = ctx;
7803
+ ctx.effect(() => {
7804
+ const style = document.createElement("style");
7805
+ style.id = "dsh-guardrails";
7806
+ style.textContent = CSS;
7807
+ document.head.appendChild(style);
7808
+ return () => style.remove();
7809
+ }, "guardrails: styles");
7810
+ ctx.effect(() => ctx.slots.inject("settings.section", () => ctx.slots.register({
7811
+ name: "settings.section",
7812
+ id: "guardrails",
7813
+ order: 35,
7814
+ label: () => "Права",
7815
+ }, PermissionsSection)), "guardrails: settings");
7816
+ }
7817
+
7818
+ exports.apply = apply;
7819
+ exports.inject = ["slots", "connection"];
7820
+ return module.exports;
7821
+ })());
7822
+
7823
+ const __partNames = ["dsh-gkb-ru-ui","dsh-token-lab","dsh-peak-guard","dsh-session-move","dsh-profile-transfer","dsh-memory-bridge","dsh-mcp-manager","dsh-agent-extensions","dsh-guardrails"];
7534
7824
  const __kitErrors = [];
7535
7825
  const __pushKitError = (label, error) => {
7536
7826
  const message = String(error && error.message ? error.message : error);
@@ -7538,7 +7828,7 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
7538
7828
  if (__kitErrors.length <= 5) console.error("[dsh-desktop-kit] registration failed:", label, message);
7539
7829
  };
7540
7830
  try {
7541
- window.__DSH_DESKTOP_KIT__ = { version: "2.7.7", parts: __partNames, errors: __kitErrors, ready: false };
7831
+ window.__DSH_DESKTOP_KIT__ = { version: "2.7.9", parts: __partNames, errors: __kitErrors, ready: false };
7542
7832
  } catch {}
7543
7833
  const __inject = [];
7544
7834
  for (const __part of __parts) {
@@ -0,0 +1,29 @@
1
+ # DSH Desktop Kit 2.7.8 — тест
2
+
3
+ Дата: 2026-11-12.
4
+
5
+ ## Permissions UI
6
+
7
+ - Настройки → **Права**.
8
+ - RPC `/api/dsh-guardrails/permissions` — read-only.
9
+ - `permissionPresets.catalog/current/resolve/optionOf` + `loadPolicy`.
10
+ - Каталог пресетов, текущий режим, sandbox/approval, жёсткие правила, защищённые пути.
11
+ - Смена режима не дублируется: только штатный picker DSH.
12
+
13
+ ## MCP prompts (из 2.7.7)
14
+
15
+ - `prompts/list`, `prompts/get`, UI-модалка, `/mcp prompts`, `/mcp prompt`.
16
+
17
+ ## Проверки
18
+
19
+ - `node tools/permissions-ui-tests.mjs` — OK.
20
+ - `node tools/mcp-manager-tests.mjs` — OK.
21
+ - `npm test` — exit 0.
22
+ - `build:client` — 9 частей; `check:client` — OK.
23
+ - browser_qa audit 390/768/1440 (Permissions): overflow 0, console 0, markup 0.
24
+ - Скриншоты: `plugins/dsh-guardrails/screenshots/permissions-1440.png`, `permissions-390.png`.
25
+
26
+ ## Ограничения
27
+
28
+ - История approvals не хранится: DSH показывает карточку в чате, kit её не дублирует.
29
+ - OAuth и sampling MCP не реализованы.
@@ -0,0 +1,28 @@
1
+ # DSH Desktop Kit 2.7.9 — тест
2
+
3
+ Дата: 2026-11-12.
4
+
5
+ ## Worktrees
6
+
7
+ - `project_worktree action=list|create|remove`.
8
+ - `.dsh/worktrees/<slug>` + branch `dsh/<slug>`; путь исключён через `.git/info/exclude`.
9
+ - Грязный worktree не удаляется без `force`; ветка остаётся.
10
+ - Тест: `tools/worktrees-tests.mjs` (создание, dirty, force remove, ветка).
11
+
12
+ ## Фоновые агенты
13
+
14
+ - `project_agent background=true` возвращает id сразу.
15
+ - `.dsh/agents/runs.json` — durable-реестр (id, childId, branch, status, preview).
16
+ - `status` и `cancel`; отмена через `subagents.interrupt` + dispose.
17
+ - Тест: `tools/background-agents-tests.mjs` (running → completed, cancel).
18
+
19
+ ## UI
20
+
21
+ - Настройки → Агенты: Worktrees и Фоновые запуски.
22
+ - browser_qa audit 390/768/1440 — overflow 0, console 0, markup 0.
23
+ - Скриншоты обновлены.
24
+
25
+ ## Полный прогон
26
+
27
+ - `npm test` — exit 0.
28
+ - `build:client` — 9 частей; `check:client` — OK.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-desktop-kit",
3
- "version": "2.7.7",
3
+ "version": "2.7.9",
4
4
  "description": "DeepSeek Harness desktop kit 2.7: 16 plugins and 30 skills — self-applying skills, token lab, Skills Studio, project memory, GitHub and USB memory transfer, guardrails, repo map, checkpoints, plan mode, browser QA, GitHub review, Claude Code-like hooks/agents/commands, Russian UI, peak guard, chat move and profile transfer.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -74,7 +74,7 @@
74
74
  "build:client": "node tools/build-client.mjs",
75
75
  "build:skills": "node tools/build-skill-manifest.mjs",
76
76
  "check:client": "node tools/check-client.mjs client.js",
77
- "test": "node tools/smoke-tests.mjs && node tools/memory-tests.mjs && node tools/skills-sync-tests.mjs && node tools/kit-upgrade-tests.mjs && node plugins/dsh-profile-transfer/test/transfer.test.mjs && node --test plugins/dsh-memory-bridge/test/bridge.test.mjs plugins/dsh-memory-bridge/test/github.test.mjs plugins/dsh-memory-bridge/test/merge.test.mjs plugins/dsh-memory-bridge/test/user-specific.test.mjs && node tools/repo-map-tests.mjs && node tools/checkpoints-tests.mjs && node tools/plan-mode-tests.mjs && node tools/browser-qa-tests.mjs && node tools/ui-gate-tests.mjs && node tools/agent-extensions-tests.mjs && node tools/mcp-manager-tests.mjs && node tools/github-review-tests.mjs && node plugins/dsh-guardrails/tools/guardrails-tests.mjs",
77
+ "test": "node tools/smoke-tests.mjs && node tools/memory-tests.mjs && node tools/skills-sync-tests.mjs && node tools/kit-upgrade-tests.mjs && node plugins/dsh-profile-transfer/test/transfer.test.mjs && node --test plugins/dsh-memory-bridge/test/bridge.test.mjs plugins/dsh-memory-bridge/test/github.test.mjs plugins/dsh-memory-bridge/test/merge.test.mjs plugins/dsh-memory-bridge/test/user-specific.test.mjs && node tools/repo-map-tests.mjs && node tools/checkpoints-tests.mjs && node tools/plan-mode-tests.mjs && node tools/browser-qa-tests.mjs && node tools/ui-gate-tests.mjs && node tools/agent-extensions-tests.mjs && node tools/mcp-manager-tests.mjs && node tools/github-review-tests.mjs && node tools/worktrees-tests.mjs && node tools/background-agents-tests.mjs && node tools/permissions-ui-tests.mjs && node plugins/dsh-guardrails/tools/guardrails-tests.mjs",
78
78
  "skills:cost": "node tools/skill-cost.mjs",
79
79
  "skills:check": "node tools/skill-cost.mjs --check",
80
80
  "release": "node tools/release.mjs"
@@ -4,7 +4,7 @@ Claude Code-like project extensions for DSH, all opt-in and project-local.
4
4
 
5
5
  ## Settings → Агенты
6
6
 
7
- Экран показывает все `.dsh/agents/*.md`: описание, enforced permission, tools и модель.
7
+ Экран показывает все `.dsh/agents/*.md`: описание, enforced permission, tools и модель. Ниже — активные **worktrees** и **фоновые запуски** с кнопкой «Остановить».
8
8
 
9
9
  ![Агенты проекта](screenshots/agents-1440.png)
10
10
 
@@ -68,6 +68,29 @@ project_agent action=run name=reviewer task="Проверь текущий ди
68
68
 
69
69
  The child runs in its own context through the DSH `subagents` service and returns only its final text.
70
70
 
71
+ Background and isolated runs:
72
+
73
+ ```text
74
+ project_agent action=run name=reviewer task="..." background=true
75
+ project_agent action=status
76
+ project_agent action=cancel id=<run-id>
77
+
78
+ project_agent action=run name=reviewer task="..." worktree=auto
79
+ project_worktree action=list
80
+ project_worktree action=create slug=feature-x base=HEAD
81
+ project_worktree action=remove slug=feature-x
82
+ ```
83
+
84
+ A background run returns immediately with a durable id and writes a small record
85
+ to `.dsh/agents/runs.json` (status, worktree/branch, short result preview; no
86
+ transcript). The child keeps running after the parent turn. `cancel` interrupts
87
+ the live child and marks the record canceled.
88
+
89
+ Worktrees live in `.dsh/worktrees/<slug>` on branch `dsh/<slug>`. The path is
90
+ added to `.git/info/exclude`, not to the tracked `.gitignore`, so the main
91
+ worktree stays clean. `remove` refuses a dirty worktree unless `force=true`;
92
+ the branch is kept for review.
93
+
71
94
  `tools`, `permission` and `model` are enforced, not just documentation:
72
95
 
73
96
  - `tools: read, grep` — the child receives a deny for any other tool;