dsh-desktop-kit 2.7.6 → 2.7.8
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 +14 -0
- package/README.md +19 -17
- package/client.js +352 -3
- package/docs/TEST-2.7.7.md +25 -0
- package/docs/TEST-2.7.8.md +29 -0
- package/package.json +2 -2
- package/plugins/dsh-guardrails/README.md +13 -0
- package/plugins/dsh-guardrails/client.js +220 -0
- package/plugins/dsh-guardrails/index.js +6 -0
- package/plugins/dsh-guardrails/lib/api.js +103 -0
- package/plugins/dsh-guardrails/package.json +2 -0
- package/plugins/dsh-guardrails/screenshots/permissions-1440.png +0 -0
- package/plugins/dsh-guardrails/screenshots/permissions-390.png +0 -0
- package/plugins/dsh-mcp-manager/README.md +13 -3
- package/plugins/dsh-mcp-manager/client.js +137 -1
- package/plugins/dsh-mcp-manager/index.js +27 -4
- package/plugins/dsh-mcp-manager/lib/api.js +33 -2
- package/plugins/dsh-mcp-manager/lib/client.js +18 -0
- package/plugins/dsh-mcp-manager/lib/manager.js +14 -2
- package/plugins/dsh-mcp-manager/screenshots/prompts-1440.png +0 -0
- package/plugins/dsh-mcp-manager/screenshots/prompts-390.png +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.7.8 (2026-11-12)
|
|
4
|
+
|
|
5
|
+
- **Permissions UI.** Настройки → **Права**: эффективный preset DSH (`current`), sandbox/approval, каталог пресетов, жёсткие правила kit и защищённые пути. Страница read-only, без второго picker и без хранения approvals.
|
|
6
|
+
- **Guardrails RPC.** `/api/dsh-guardrails/permissions`: `catalog/current/resolve/optionOf` + снимок политики; привязка к сессии и workspace.
|
|
7
|
+
- **UI.** 390/768/1440: overflow 0, console 0, markup 0; скриншоты 1440/390.
|
|
8
|
+
- **2.7.7** (staged) содержала MCP prompts; 2.7.8 включает и prompts, и Permissions UI.
|
|
9
|
+
|
|
10
|
+
## 2.7.7 (2026-11-12)
|
|
11
|
+
|
|
12
|
+
- **MCP prompts.** Settings → MCP → **Prompts**: список промптов сервера, аргументы, запуск, результат и копирование. Новые RPC `prompts`/`prompt`; клиент поддерживает `prompts/list` и `prompts/get` с пагинацией.
|
|
13
|
+
- **`/mcp prompts` и `/mcp prompt`.** Промпты доступны и из командной строки, включая JSON-аргументы.
|
|
14
|
+
- **UI.** Модалка prompts проверена на 390/768/1440: overflow 0, console 0, markup 0.
|
|
15
|
+
- **README.** Скриншоты prompts 1440/390 и описание.
|
|
16
|
+
|
|
3
17
|
## 2.7.6 (2026-11-11)
|
|
4
18
|
|
|
5
19
|
- **README images через unpkg.** Npm-страница не разрешает относительные пути, а GitHub raw и jsDelivr в этой сети не открываются. Все 15 изображений переведены на `https://unpkg.com/dsh-desktop-kit@2.7.4/...`; загрузка проверена в headless Chrome.
|
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
|
-

|
|
8
8
|
|
|
9
9
|
## MCP: подключения без JSON
|
|
10
10
|
|
|
11
|
-
Настройки → **MCP**: пресеты GitHub, PostgreSQL и Filesystem, живые статусы, health, список tools и безопасное хранение токенов в `~/.dsh/mcp.secrets.json`.
|
|
11
|
+
Настройки → **MCP**: пресеты GitHub, PostgreSQL и Filesystem, живые статусы, health, список tools и безопасное хранение токенов в `~/.dsh/mcp.secrets.json`. Промпты сервера открываются кнопкой **Prompts**: аргументы, запуск и копирование результата.
|
|
12
12
|
|
|
13
|
-

|
|
14
14
|
|
|
15
|
-

|
|
16
16
|
|
|
17
|
-

|
|
18
18
|
|
|
19
19
|
## Права и субагенты
|
|
20
20
|
|
|
@@ -23,9 +23,11 @@
|
|
|
23
23
|
- **Approval** — штатная карточка DSH: Allow once / Always allow / Reject.
|
|
24
24
|
- **`.dsh/`** создаётся автоматически: agents, commands, rules, hooks, verify, mcp, PROJECT.md.
|
|
25
25
|
|
|
26
|
-

|
|
27
27
|
|
|
28
|
-
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
**v2.7.8** · MIT · проверено на DSH `0.2.1-rc.5` и `0.2.2-rc.1` (Windows 11) · без внешних зависимостей и телеметрии
|
|
29
31
|
|
|
30
32
|
**План развития:** [docs/ROADMAP-3.0.md](docs/ROADMAP-3.0.md) — Memory Fabric, семантическое
|
|
31
33
|
слияние, локальный индекс памяти, шифрованные переносы, Memory Hub и npm trusted publishing.
|
|
@@ -113,27 +115,27 @@
|
|
|
113
115
|
- перед каждой синхронизацией проверяется, что репозиторий всё ещё приватный;
|
|
114
116
|
- секреты и PII вычищаются, домашние пути заменяются токеном `%DSH_HOME%`; LLM не вызывается — **0 токенов**.
|
|
115
117
|
|
|
116
|
-

|
|
117
119
|
|
|
118
|
-

|
|
119
121
|
|
|
120
122
|
## Скриншоты
|
|
121
123
|
|
|
122
124
|
### Skills Studio: каталог, стоимость и редактор
|
|
123
125
|
|
|
124
|
-

|
|
125
127
|
|
|
126
|
-

|
|
127
129
|
|
|
128
130
|
### Token Lab: куда уходит контекст
|
|
129
131
|
|
|
130
|
-

|
|
131
133
|
|
|
132
134
|
### Тёмная тема и мобильный
|
|
133
135
|
|
|
134
|
-

|
|
135
137
|
|
|
136
|
-

|
|
137
139
|
|
|
138
140
|
## Окно-напоминание: пиковый тариф
|
|
139
141
|
|
|
@@ -143,11 +145,11 @@ DeepSeek включает двойной тариф в пиковые часы (
|
|
|
143
145
|
- **в окне** — красная карточка: причина, границы окна в местном времени и UTC, живой отсчёт до конца и рекомендация, когда вернуться;
|
|
144
146
|
- **после окна** — карточка исчезает; состояние всегда доступно через `window.__DSH_PEAK_GUARD__.state()`.
|
|
145
147
|
|
|
146
|
-

|
|
147
149
|
|
|
148
|
-

|
|
149
151
|
|
|
150
|
-

|
|
151
153
|
|
|
152
154
|
Подробности и настройки — [plugins/dsh-peak-guard/README.md](plugins/dsh-peak-guard/README.md).
|
|
153
155
|
|
package/client.js
CHANGED
|
@@ -6929,6 +6929,12 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
6929
6929
|
.mcp-tool{display:flex;gap:8px;align-items:baseline;font-size:12px;line-height:1.45;min-width:0}
|
|
6930
6930
|
.mcp-tool code{color:var(--mcp-accent);font:inherit;font-weight:650;white-space:nowrap}
|
|
6931
6931
|
.mcp-tool span{color:var(--mcp-muted);overflow:hidden;text-overflow:ellipsis}
|
|
6932
|
+
.mcp-prompt-list{display:flex;flex-direction:column;gap:8px}
|
|
6933
|
+
.mcp-prompt-card{text-align:left;border:1px solid var(--mcp-line);background:var(--mcp-panel);border-radius:12px;padding:11px 12px;cursor:pointer;font:inherit;color:inherit;min-height:44px}
|
|
6934
|
+
.mcp-prompt-card:hover{border-color:rgba(15,118,110,.4);background:var(--mcp-accent-soft)}
|
|
6935
|
+
.mcp-prompt-card b{display:block;font-size:13px}
|
|
6936
|
+
.mcp-prompt-card span{display:block;color:var(--mcp-muted);font-size:12px;line-height:1.4;margin-top:3px}
|
|
6937
|
+
.mcp-prompt-output{margin:0;padding:12px;border:1px solid var(--mcp-line);border-radius:10px;background:var(--mcp-soft);color:var(--mcp-text);font:12px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere;max-height:38vh;overflow:auto}
|
|
6932
6938
|
.mcp-empty{border:1px dashed var(--mcp-line);border-radius:18px;padding:26px;background:linear-gradient(160deg,var(--mcp-soft),transparent 75%);display:flex;flex-direction:column;gap:12px;align-items:flex-start}
|
|
6933
6939
|
.mcp-empty h3{margin:0;font-size:18px;letter-spacing:-.015em}
|
|
6934
6940
|
.mcp-empty p{margin:0;color:var(--mcp-muted);font-size:13px;line-height:1.5;max-width:60ch}
|
|
@@ -6983,7 +6989,7 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
6983
6989
|
);
|
|
6984
6990
|
}
|
|
6985
6991
|
|
|
6986
|
-
function ServerCard({ server, busy, onToggle, onTest, onTools, onRemove, tools }) {
|
|
6992
|
+
function ServerCard({ server, busy, onToggle, onTest, onTools, onPrompts, onRemove, tools }) {
|
|
6987
6993
|
const [open, setOpen] = useState(false);
|
|
6988
6994
|
const connected = server.connected;
|
|
6989
6995
|
const status = connected ? "on" : (server.error ? "err" : "off");
|
|
@@ -7006,6 +7012,7 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
7006
7012
|
h(Toggle, { checked: server.trusted, label: "Доверен", disabled: busy, onChange: (value) => onToggle(server.id, { trusted: value }) }),
|
|
7007
7013
|
h("div", { className: "mcp-actions" },
|
|
7008
7014
|
h("button", { className: "mcp-btn ghost", disabled: busy, onClick: () => onTest(server.id) }, "Проверить"),
|
|
7015
|
+
h("button", { className: "mcp-btn ghost", disabled: busy, onClick: () => onPrompts(server.id) }, "Prompts"),
|
|
7009
7016
|
h("button", { className: "mcp-btn ghost", disabled: busy, onClick: () => { setOpen(!open); if (!open) onTools(server.id); } }, open ? "Скрыть" : "Tools"),
|
|
7010
7017
|
h("button", { className: "mcp-btn danger ghost", disabled: busy, onClick: () => onRemove(server.id) }, "Удалить"),
|
|
7011
7018
|
),
|
|
@@ -7021,6 +7028,58 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
7021
7028
|
);
|
|
7022
7029
|
}
|
|
7023
7030
|
|
|
7031
|
+
function PromptModal({ state, onClose, onPick, onBack, onArg, onRun, onCopy }) {
|
|
7032
|
+
if (!state) return null;
|
|
7033
|
+
const prompt = state.prompt;
|
|
7034
|
+
const args = Array.isArray(prompt?.arguments) ? prompt.arguments : [];
|
|
7035
|
+
return h("div", { className: "mcp-overlay", onClick: onClose },
|
|
7036
|
+
h("div", {
|
|
7037
|
+
className: "mcp-modal",
|
|
7038
|
+
role: "dialog",
|
|
7039
|
+
"aria-modal": "true",
|
|
7040
|
+
"aria-labelledby": "mcp-prompts-title",
|
|
7041
|
+
onClick: (event) => event.stopPropagation(),
|
|
7042
|
+
},
|
|
7043
|
+
h("div", { className: "mcp-modal-head" },
|
|
7044
|
+
h("div", null,
|
|
7045
|
+
h("h2", { id: "mcp-prompts-title" }, prompt ? (prompt.title || prompt.name) : "Prompts"),
|
|
7046
|
+
h("p", null, prompt ? (prompt.description || "Промпт MCP-сервера") : `Промпты сервера «${state.serverId}».`),
|
|
7047
|
+
),
|
|
7048
|
+
h("button", { className: "mcp-btn ghost", type: "button", onClick: onClose, "aria-label": "Закрыть" }, "Закрыть"),
|
|
7049
|
+
),
|
|
7050
|
+
state.error ? h("div", { className: "mcp-error", role: "alert" }, state.error) : null,
|
|
7051
|
+
!prompt ? h("div", { className: "mcp-prompt-list" },
|
|
7052
|
+
state.prompts.length === 0 ? h("div", { className: "mcp-meta" }, "У сервера нет prompts.") : state.prompts.map((item) => h("button", {
|
|
7053
|
+
key: item.name,
|
|
7054
|
+
type: "button",
|
|
7055
|
+
className: "mcp-prompt-card",
|
|
7056
|
+
onClick: () => onPick(item),
|
|
7057
|
+
}, h("b", null, item.name), h("span", null, item.description || "Без описания"))),
|
|
7058
|
+
) : h("div", { className: "mcp-form" },
|
|
7059
|
+
args.map((arg, index) => h("div", { className: `mcp-field${String(arg.description || "").length > 40 ? " wide" : ""}`, key: arg.name },
|
|
7060
|
+
h("label", { htmlFor: `mcp-prompt-arg-${index}` }, arg.name, arg.required ? " *" : ""),
|
|
7061
|
+
h("input", {
|
|
7062
|
+
id: `mcp-prompt-arg-${index}`,
|
|
7063
|
+
value: state.args[arg.name] ?? "",
|
|
7064
|
+
placeholder: arg.description || "",
|
|
7065
|
+
onChange: (event) => onArg(arg.name, event.target.value),
|
|
7066
|
+
}),
|
|
7067
|
+
)),
|
|
7068
|
+
args.length === 0 ? h("p", { className: "mcp-note", style: { gridColumn: "1/-1" } }, "У промпта нет аргументов.") : null,
|
|
7069
|
+
state.result ? h("div", { className: "mcp-field wide" },
|
|
7070
|
+
h("label", null, "Результат"),
|
|
7071
|
+
h("pre", { className: "mcp-prompt-output" }, state.result),
|
|
7072
|
+
) : null,
|
|
7073
|
+
h("div", { className: "mcp-form-actions", style: { gridColumn: "1/-1" } },
|
|
7074
|
+
state.prompts.length > 1 ? h("button", { className: "mcp-btn ghost", type: "button", onClick: onBack }, "Назад") : null,
|
|
7075
|
+
state.result ? h("button", { className: "mcp-btn ghost", type: "button", onClick: onCopy }, "Копировать") : null,
|
|
7076
|
+
h("button", { className: "mcp-btn primary", type: "button", disabled: state.busy, onClick: onRun }, state.busy ? "Запрашиваем…" : "Получить"),
|
|
7077
|
+
),
|
|
7078
|
+
),
|
|
7079
|
+
),
|
|
7080
|
+
);
|
|
7081
|
+
}
|
|
7082
|
+
|
|
7024
7083
|
function ServerForm({ initial, busy, onCancel, onSave }) {
|
|
7025
7084
|
const [form, setForm] = useState(initial);
|
|
7026
7085
|
const set = (patch) => setForm((current) => ({ ...current, ...patch }));
|
|
@@ -7100,6 +7159,7 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
7100
7159
|
const [form, setForm] = useState(null);
|
|
7101
7160
|
const [tools, setTools] = useState({});
|
|
7102
7161
|
const [toast, setToast] = useState("");
|
|
7162
|
+
const [promptModal, setPromptModal] = useState(null);
|
|
7103
7163
|
|
|
7104
7164
|
const load = async () => {
|
|
7105
7165
|
try {
|
|
@@ -7127,6 +7187,72 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
7127
7187
|
}
|
|
7128
7188
|
};
|
|
7129
7189
|
|
|
7190
|
+
const openPrompts = async (id) => {
|
|
7191
|
+
setBusy(true);
|
|
7192
|
+
setError(null);
|
|
7193
|
+
try {
|
|
7194
|
+
const result = await call(window.__DSH_MCP_CTX__, "prompts", { id });
|
|
7195
|
+
setPromptModal({
|
|
7196
|
+
serverId: id,
|
|
7197
|
+
prompts: Array.isArray(result.prompts) ? result.prompts : [],
|
|
7198
|
+
prompt: null,
|
|
7199
|
+
args: {},
|
|
7200
|
+
result: "",
|
|
7201
|
+
busy: false,
|
|
7202
|
+
error: result.error || "",
|
|
7203
|
+
});
|
|
7204
|
+
} catch (cause) {
|
|
7205
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
7206
|
+
} finally {
|
|
7207
|
+
setBusy(false);
|
|
7208
|
+
}
|
|
7209
|
+
};
|
|
7210
|
+
|
|
7211
|
+
const pickPrompt = (prompt) => {
|
|
7212
|
+
setPromptModal((current) => current ? {
|
|
7213
|
+
...current,
|
|
7214
|
+
prompt,
|
|
7215
|
+
args: Object.fromEntries((prompt.arguments ?? []).map((arg) => [arg.name, ""])),
|
|
7216
|
+
result: "",
|
|
7217
|
+
error: "",
|
|
7218
|
+
} : current);
|
|
7219
|
+
};
|
|
7220
|
+
|
|
7221
|
+
const runPrompt = async () => {
|
|
7222
|
+
const current = promptModal;
|
|
7223
|
+
if (!current?.prompt) return;
|
|
7224
|
+
setPromptModal({ ...current, busy: true, error: "", result: "" });
|
|
7225
|
+
try {
|
|
7226
|
+
const result = await call(window.__DSH_MCP_CTX__, "prompt", {
|
|
7227
|
+
id: current.serverId,
|
|
7228
|
+
name: current.prompt.name,
|
|
7229
|
+
arguments: current.args,
|
|
7230
|
+
});
|
|
7231
|
+
setPromptModal((latest) => latest ? {
|
|
7232
|
+
...latest,
|
|
7233
|
+
busy: false,
|
|
7234
|
+
result: result.text || "",
|
|
7235
|
+
error: result.text ? "" : "Сервер вернул пустой промпт",
|
|
7236
|
+
} : latest);
|
|
7237
|
+
} catch (cause) {
|
|
7238
|
+
setPromptModal((latest) => latest ? {
|
|
7239
|
+
...latest,
|
|
7240
|
+
busy: false,
|
|
7241
|
+
error: cause instanceof Error ? cause.message : String(cause),
|
|
7242
|
+
} : latest);
|
|
7243
|
+
}
|
|
7244
|
+
};
|
|
7245
|
+
|
|
7246
|
+
const copyPrompt = async () => {
|
|
7247
|
+
try {
|
|
7248
|
+
await navigator.clipboard.writeText(promptModal?.result ?? "");
|
|
7249
|
+
setToast("Промпт скопирован");
|
|
7250
|
+
setTimeout(() => setToast(""), 2500);
|
|
7251
|
+
} catch {
|
|
7252
|
+
setError("Не удалось скопировать — выделите текст вручную");
|
|
7253
|
+
}
|
|
7254
|
+
};
|
|
7255
|
+
|
|
7130
7256
|
const presets = PRESETS;
|
|
7131
7257
|
const openPreset = (preset) => setForm({
|
|
7132
7258
|
edit: false,
|
|
@@ -7198,11 +7324,21 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
7198
7324
|
if (result.error) setError(result.error);
|
|
7199
7325
|
} catch (cause) { setError(cause instanceof Error ? cause.message : String(cause)); }
|
|
7200
7326
|
},
|
|
7327
|
+
onPrompts: openPrompts,
|
|
7201
7328
|
onRemove: (id) => {
|
|
7202
7329
|
if (window.confirm(`Удалить MCP-сервер «${id}»?`)) void run("remove", { id }, "Сервер удалён");
|
|
7203
7330
|
},
|
|
7204
7331
|
}))) : null,
|
|
7205
7332
|
servers.length > 0 ? h("p", { className: "mcp-note" }, `Секреты: ${data?.secretsFile || "~/.dsh/mcp.secrets.json"} · новые tools появятся в новом чате после подключения.`) : null,
|
|
7333
|
+
h(PromptModal, {
|
|
7334
|
+
state: promptModal,
|
|
7335
|
+
onClose: () => setPromptModal(null),
|
|
7336
|
+
onPick: pickPrompt,
|
|
7337
|
+
onBack: () => setPromptModal((current) => current ? { ...current, prompt: null, args: {}, result: "", error: "" } : current),
|
|
7338
|
+
onArg: (name, value) => setPromptModal((current) => current ? { ...current, args: { ...current.args, [name]: value } } : current),
|
|
7339
|
+
onRun: runPrompt,
|
|
7340
|
+
onCopy: copyPrompt,
|
|
7341
|
+
}),
|
|
7206
7342
|
form ? h(ServerForm, {
|
|
7207
7343
|
initial: form,
|
|
7208
7344
|
busy,
|
|
@@ -7394,7 +7530,220 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
7394
7530
|
return module.exports;
|
|
7395
7531
|
})());
|
|
7396
7532
|
|
|
7397
|
-
|
|
7533
|
+
/* ---- dsh-guardrails ---- */
|
|
7534
|
+
__parts.push((function () {
|
|
7535
|
+
var module = { exports: {} };
|
|
7536
|
+
var exports = module.exports;
|
|
7537
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7538
|
+
const React = require("react");
|
|
7539
|
+
const h = React.createElement;
|
|
7540
|
+
const useEffect = React.useEffect;
|
|
7541
|
+
const useState = React.useState;
|
|
7542
|
+
|
|
7543
|
+
const CSS = `
|
|
7544
|
+
.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)}
|
|
7545
|
+
.pr-head{display:flex;align-items:flex-end;justify-content:space-between;gap:20px;flex-wrap:wrap}
|
|
7546
|
+
.pr-eyebrow{margin:0 0 6px;font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--pr-accent)}
|
|
7547
|
+
.pr-title{margin:0;font-size:clamp(24px,2.6vw,32px);line-height:1.1;letter-spacing:-.025em;font-weight:680}
|
|
7548
|
+
.pr-sub{margin:8px 0 0;max-width:68ch;color:var(--pr-muted);font-size:14px;line-height:1.5}
|
|
7549
|
+
.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}
|
|
7550
|
+
.pr-mode-top{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap}
|
|
7551
|
+
.pr-mode h3{margin:0;font-size:24px;line-height:1.1;letter-spacing:-.03em}
|
|
7552
|
+
.pr-chips{display:flex;gap:6px;flex-wrap:wrap}
|
|
7553
|
+
.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}
|
|
7554
|
+
.pr-chip.sandbox{color:#0f766e;border-color:rgba(15,118,110,.25);background:rgba(15,118,110,.07)}
|
|
7555
|
+
.pr-chip.approval{color:#9a3412;border-color:rgba(154,52,18,.22);background:rgba(154,52,18,.06)}
|
|
7556
|
+
.pr-mode p{margin:8px 0 0;color:var(--pr-muted);font-size:13px;line-height:1.5;max-width:70ch}
|
|
7557
|
+
.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}
|
|
7558
|
+
.pr-note b{color:var(--pr-text);font-weight:650}
|
|
7559
|
+
.pr-stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
|
|
7560
|
+
.pr-stat{padding:14px 16px;border:1px solid var(--pr-line);border-radius:14px;background:var(--pr-panel);min-width:0}
|
|
7561
|
+
.pr-stat b{display:block;font-size:24px;line-height:1.1;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
|
|
7562
|
+
.pr-stat span{display:block;margin-top:5px;color:var(--pr-muted);font-size:11px;font-weight:650;letter-spacing:.07em;text-transform:uppercase}
|
|
7563
|
+
.pr-section{display:flex;flex-direction:column;gap:10px;min-width:0}
|
|
7564
|
+
.pr-section h3{margin:0;font-size:15px;letter-spacing:-.01em}
|
|
7565
|
+
.pr-section .pr-hint{margin:0;color:var(--pr-muted);font-size:12px;line-height:1.45}
|
|
7566
|
+
.pr-presets{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
|
|
7567
|
+
.pr-preset{padding:12px;border:1px solid var(--pr-line);border-radius:12px;background:var(--pr-panel);min-width:0}
|
|
7568
|
+
.pr-preset.active{border-color:rgba(15,118,110,.35);background:rgba(15,118,110,.06)}
|
|
7569
|
+
.pr-preset b{display:flex;justify-content:space-between;gap:8px;font-size:13px;align-items:center}
|
|
7570
|
+
.pr-preset span{display:block;margin-top:4px;color:var(--pr-muted);font-size:11.5px;line-height:1.4}
|
|
7571
|
+
.pr-preset em{font-style:normal;color:var(--pr-accent);font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}
|
|
7572
|
+
.pr-list{display:flex;flex-direction:column;border:1px solid var(--pr-line);border-radius:14px;overflow:hidden;background:var(--pr-panel)}
|
|
7573
|
+
.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}
|
|
7574
|
+
.pr-row:first-child{border-top:0}
|
|
7575
|
+
.pr-sev{flex:none;width:46px;font-size:9.5px;font-weight:750;letter-spacing:.08em;text-transform:uppercase}
|
|
7576
|
+
.pr-sev.deny{color:#9f1239}.pr-sev.ask{color:#9a3412}
|
|
7577
|
+
.pr-row code{flex:none;color:var(--pr-text);font:inherit;font-weight:650;white-space:nowrap}
|
|
7578
|
+
.pr-row span{color:var(--pr-muted);overflow:hidden;text-overflow:ellipsis;min-width:0}
|
|
7579
|
+
.pr-path{padding:9px 12px;border-top:1px solid var(--pr-line);font-size:12px;line-height:1.45;min-width:0}
|
|
7580
|
+
.pr-path:first-child{border-top:0}
|
|
7581
|
+
.pr-path b{display:block;font-size:12px}
|
|
7582
|
+
.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}
|
|
7583
|
+
.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}
|
|
7584
|
+
.pr-loading{color:var(--pr-muted);font-size:13px;padding:18px 0}
|
|
7585
|
+
.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%)}
|
|
7586
|
+
.pr-foot{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;color:var(--pr-muted);font-size:11px;line-height:1.4}
|
|
7587
|
+
@media(max-width:860px){.pr-presets{grid-template-columns:1fr 1fr}.pr-stats{grid-template-columns:1fr 1fr}}
|
|
7588
|
+
@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}}
|
|
7589
|
+
`;
|
|
7590
|
+
|
|
7591
|
+
const SEVERITY_ORDER = { deny: 0, ask: 1 };
|
|
7592
|
+
|
|
7593
|
+
function currentSessionSelector(state) {
|
|
7594
|
+
try {
|
|
7595
|
+
const sessions = Object.values(state.byId || {});
|
|
7596
|
+
const main = sessions.find((session) => session && session.retainedBy && (session.retainedBy.mainView || 0) > 0);
|
|
7597
|
+
if (main) return main.id;
|
|
7598
|
+
const ids = Array.isArray(state.ids) ? state.ids : [];
|
|
7599
|
+
return ids.length > 0 ? ids[ids.length - 1] : undefined;
|
|
7600
|
+
} catch { return undefined; }
|
|
7601
|
+
}
|
|
7602
|
+
|
|
7603
|
+
async function call(ctx, operation, payload) {
|
|
7604
|
+
let signal;
|
|
7605
|
+
try { signal = AbortSignal.timeout(30000); } catch { signal = undefined; }
|
|
7606
|
+
const result = await ctx.connection.rpc.call("/api", `dsh-guardrails/${operation}`, payload ?? {}, signal);
|
|
7607
|
+
if (!result || result.ok !== true) throw new Error(result?.error?.message || "Ошибка RPC");
|
|
7608
|
+
return result.value;
|
|
7609
|
+
}
|
|
7610
|
+
|
|
7611
|
+
function errorOf(value) { return value instanceof Error ? value.message : String(value); }
|
|
7612
|
+
|
|
7613
|
+
function SandboxChip({ value }) {
|
|
7614
|
+
const labels = {
|
|
7615
|
+
"read-only": "Read only",
|
|
7616
|
+
"workspace-write": "Workspace write",
|
|
7617
|
+
"danger-full-access": "Full access",
|
|
7618
|
+
};
|
|
7619
|
+
return h("span", { className: "pr-chip sandbox" }, labels[value] || value || "sandbox");
|
|
7620
|
+
}
|
|
7621
|
+
|
|
7622
|
+
function PermissionsSection(props) {
|
|
7623
|
+
const [state, setState] = useState({ loading: true, error: null, data: null });
|
|
7624
|
+
const useSessions = props && props.useSessions;
|
|
7625
|
+
const useWorkspaces = props && props.useWorkspaces;
|
|
7626
|
+
const sessionId = useSessions ? useSessions(currentSessionSelector) : undefined;
|
|
7627
|
+
const workspaceSnapshot = useWorkspaces ? useWorkspaces((snapshot) => snapshot) : undefined;
|
|
7628
|
+
const workspaces = Array.isArray(workspaceSnapshot?.items) ? workspaceSnapshot.items : [];
|
|
7629
|
+
const workspace = workspaces.find((item) => Array.isArray(item.sessionIds) && item.sessionIds.includes(sessionId))
|
|
7630
|
+
?? workspaces.find((item) => Array.isArray(item.sessionIds) && item.sessionIds.length > 0)
|
|
7631
|
+
?? workspaces[0];
|
|
7632
|
+
const cwd = typeof workspace?.path === "string" ? workspace.path : undefined;
|
|
7633
|
+
|
|
7634
|
+
useEffect(() => {
|
|
7635
|
+
let cancelled = false;
|
|
7636
|
+
(async () => {
|
|
7637
|
+
try {
|
|
7638
|
+
const data = await call(window.__DSH_GUARDRAILS_CTX__, "permissions", { sessionId, cwd });
|
|
7639
|
+
if (!cancelled) setState({ loading: false, error: null, data });
|
|
7640
|
+
} catch (error) {
|
|
7641
|
+
if (!cancelled) setState({ loading: false, error: errorOf(error), data: null });
|
|
7642
|
+
}
|
|
7643
|
+
})();
|
|
7644
|
+
return () => { cancelled = true; };
|
|
7645
|
+
}, [sessionId, cwd]);
|
|
7646
|
+
|
|
7647
|
+
const preset = state.data?.preset;
|
|
7648
|
+
const guard = state.data?.guard;
|
|
7649
|
+
const options = Array.isArray(preset?.catalog?.options) ? preset.catalog.options : [];
|
|
7650
|
+
const rules = Array.isArray(guard?.rules)
|
|
7651
|
+
? [...guard.rules].sort((a, b) => (SEVERITY_ORDER[a.severity] ?? 9) - (SEVERITY_ORDER[b.severity] ?? 9))
|
|
7652
|
+
: [];
|
|
7653
|
+
const protectedPaths = Array.isArray(guard?.protectedPaths) ? guard.protectedPaths : [];
|
|
7654
|
+
const current = preset?.current || "custom";
|
|
7655
|
+
const spec = preset?.spec;
|
|
7656
|
+
const option = preset?.option;
|
|
7657
|
+
const title = option?.name || current;
|
|
7658
|
+
|
|
7659
|
+
return h("section", { className: "pr-root", "aria-label": "Права и ограничения" },
|
|
7660
|
+
h("div", { className: "pr-head" },
|
|
7661
|
+
h("div", null,
|
|
7662
|
+
h("p", { className: "pr-eyebrow" }, "Guardrails · effective permissions"),
|
|
7663
|
+
h("h2", { className: "pr-title" }, "Права"),
|
|
7664
|
+
h("p", { className: "pr-sub" }, "Что разрешено в этом чате и что kit запрещает в любом режиме. Режим переключается штатным picker DSH в композере — здесь только прозрачный срез."),
|
|
7665
|
+
),
|
|
7666
|
+
),
|
|
7667
|
+
state.error ? h("div", { className: "pr-error", role: "alert" }, state.error) : null,
|
|
7668
|
+
state.loading ? h("div", { className: "pr-loading" }, "Читаем эффективные права…") : null,
|
|
7669
|
+
!state.loading && state.data ? h(React.Fragment, null,
|
|
7670
|
+
h("div", { className: "pr-mode" },
|
|
7671
|
+
h("div", { className: "pr-mode-top" },
|
|
7672
|
+
h("div", null,
|
|
7673
|
+
h("h3", null, title),
|
|
7674
|
+
h("p", null, option?.description || spec?.description || "Эффективный режим этого чата."),
|
|
7675
|
+
),
|
|
7676
|
+
h("div", { className: "pr-chips" },
|
|
7677
|
+
h(SandboxChip, { value: spec?.sandbox }),
|
|
7678
|
+
h("span", { className: "pr-chip approval" }, spec?.approval === "never" ? "approval never" : "approval ask"),
|
|
7679
|
+
),
|
|
7680
|
+
),
|
|
7681
|
+
h("div", { className: "pr-note" },
|
|
7682
|
+
h("b", null, "Не второй picker."),
|
|
7683
|
+
h("span", null, "Смена режима — только в DSH. Эта страница не меняет разрешения и не хранит approvals; каждая карточка подтверждения остаётся в чате."),
|
|
7684
|
+
),
|
|
7685
|
+
),
|
|
7686
|
+
h("div", { className: "pr-stats" },
|
|
7687
|
+
h("div", { className: "pr-stat" }, h("b", null, rules.length), h("span", null, "жёстких правил")),
|
|
7688
|
+
h("div", { className: "pr-stat" }, h("b", null, protectedPaths.length), h("span", null, "защищённых путей")),
|
|
7689
|
+
h("div", { className: "pr-stat" }, h("b", null, options.length), h("span", null, "пресетов DSH")),
|
|
7690
|
+
),
|
|
7691
|
+
h("div", { className: "pr-section" },
|
|
7692
|
+
h("h3", null, "Пресеты DSH"),
|
|
7693
|
+
h("p", { className: "pr-hint" }, "Доступны в композере. Текущий выделен; kit не дублирует переключатель."),
|
|
7694
|
+
h("div", { className: "pr-presets" }, options.map((item) => h("div", { key: item.value, className: `pr-preset${item.value === current ? " active" : ""}` },
|
|
7695
|
+
h("b", null, h("span", null, item.name || item.value), item.value === current ? h("em", null, "сейчас") : null),
|
|
7696
|
+
h("span", null, item.description || "Без описания"),
|
|
7697
|
+
))),
|
|
7698
|
+
),
|
|
7699
|
+
h("div", { className: "pr-section" },
|
|
7700
|
+
h("h3", null, "Жёсткие правила kit"),
|
|
7701
|
+
h("p", { className: "pr-hint" }, "Действуют даже в Full access. Отключаются только политикой guardrails, не режимом DSH."),
|
|
7702
|
+
rules.length > 0 ? h("div", { className: "pr-list" }, rules.map((rule) => h("div", { className: "pr-row", key: rule.id },
|
|
7703
|
+
h("span", { className: `pr-sev ${rule.severity}` }, rule.severity),
|
|
7704
|
+
h("code", null, rule.id),
|
|
7705
|
+
h("span", { title: rule.description }, rule.description || ""),
|
|
7706
|
+
))) : h("div", { className: "pr-empty" }, "Правила отключены политикой guardrails."),
|
|
7707
|
+
),
|
|
7708
|
+
h("div", { className: "pr-section" },
|
|
7709
|
+
h("h3", null, "Защищённые пути"),
|
|
7710
|
+
h("p", { className: "pr-hint" }, "Запись, редактирование и удаление этих путей отклоняются до выполнения."),
|
|
7711
|
+
protectedPaths.length > 0 ? h("div", { className: "pr-list" }, protectedPaths.map((entry) => h("div", { className: "pr-path", key: entry.id },
|
|
7712
|
+
h("b", null, entry.id),
|
|
7713
|
+
h("code", null, `${entry.match} /${entry.pattern}/`),
|
|
7714
|
+
))) : h("div", { className: "pr-empty" }, "Защищённые пути отключены."),
|
|
7715
|
+
),
|
|
7716
|
+
) : null,
|
|
7717
|
+
h("div", { className: "pr-foot" },
|
|
7718
|
+
h("span", null, guard?.policyPath ? `Политика: ${guard.policyPath}` : "Политика guardrails"),
|
|
7719
|
+
h("span", null, guard ? (guard.enabled ? "hooks активны" : "hooks отключены") : ""),
|
|
7720
|
+
),
|
|
7721
|
+
);
|
|
7722
|
+
}
|
|
7723
|
+
|
|
7724
|
+
function apply(ctx) {
|
|
7725
|
+
window.__DSH_GUARDRAILS_CTX__ = ctx;
|
|
7726
|
+
ctx.effect(() => {
|
|
7727
|
+
const style = document.createElement("style");
|
|
7728
|
+
style.id = "dsh-guardrails";
|
|
7729
|
+
style.textContent = CSS;
|
|
7730
|
+
document.head.appendChild(style);
|
|
7731
|
+
return () => style.remove();
|
|
7732
|
+
}, "guardrails: styles");
|
|
7733
|
+
ctx.effect(() => ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
7734
|
+
name: "settings.section",
|
|
7735
|
+
id: "guardrails",
|
|
7736
|
+
order: 35,
|
|
7737
|
+
label: () => "Права",
|
|
7738
|
+
}, PermissionsSection)), "guardrails: settings");
|
|
7739
|
+
}
|
|
7740
|
+
|
|
7741
|
+
exports.apply = apply;
|
|
7742
|
+
exports.inject = ["slots", "connection"];
|
|
7743
|
+
return module.exports;
|
|
7744
|
+
})());
|
|
7745
|
+
|
|
7746
|
+
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"];
|
|
7398
7747
|
const __kitErrors = [];
|
|
7399
7748
|
const __pushKitError = (label, error) => {
|
|
7400
7749
|
const message = String(error && error.message ? error.message : error);
|
|
@@ -7402,7 +7751,7 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
7402
7751
|
if (__kitErrors.length <= 5) console.error("[dsh-desktop-kit] registration failed:", label, message);
|
|
7403
7752
|
};
|
|
7404
7753
|
try {
|
|
7405
|
-
window.__DSH_DESKTOP_KIT__ = { version: "2.7.
|
|
7754
|
+
window.__DSH_DESKTOP_KIT__ = { version: "2.7.8", parts: __partNames, errors: __kitErrors, ready: false };
|
|
7406
7755
|
} catch {}
|
|
7407
7756
|
const __inject = [];
|
|
7408
7757
|
for (const __part of __parts) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# DSH Desktop Kit 2.7.7 — тест
|
|
2
|
+
|
|
3
|
+
Дата: 2026-11-12.
|
|
4
|
+
|
|
5
|
+
## MCP prompts
|
|
6
|
+
|
|
7
|
+
- `McpClient.listPrompts()` — `prompts/list` с пагинацией, только при capability `prompts`.
|
|
8
|
+
- `McpClient.getPrompt()` — `prompts/get` с аргументами.
|
|
9
|
+
- `McpManager.prompts/prompt`.
|
|
10
|
+
- RPC Settings: `prompts`, `prompt`.
|
|
11
|
+
- Команды: `/mcp prompts <server>`, `/mcp prompt <server> <name> [json]`.
|
|
12
|
+
- UI: модалка со списком, полями аргументов, результатом и копированием.
|
|
13
|
+
|
|
14
|
+
## Проверки
|
|
15
|
+
|
|
16
|
+
- `node tools/mcp-manager-tests.mjs` — OK, включая prompts/list и prompts/get.
|
|
17
|
+
- `npm test` — exit 0.
|
|
18
|
+
- `npm run build:client` / `check:client` — OK.
|
|
19
|
+
- browser_qa audit 390/768/1440 — overflow 0, console 0, markup 0.
|
|
20
|
+
- Скриншоты: `plugins/dsh-mcp-manager/screenshots/prompts-1440.png`, `prompts-390.png`.
|
|
21
|
+
|
|
22
|
+
## Ограничения
|
|
23
|
+
|
|
24
|
+
- OAuth и sampling по-прежнему не реализованы.
|
|
25
|
+
- Аргументы промпта — строковые поля; вложенные объекты задаются через `/mcp prompt` JSON.
|
|
@@ -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 не реализованы.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-desktop-kit",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.8",
|
|
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/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"
|
|
@@ -19,6 +19,19 @@ bundled plugins. The kit passes no services to it beyond `tools`; the
|
|
|
19
19
|
`fs/*` hooks are event-only, which is why no `fs` injection is declared
|
|
20
20
|
(the shipped `dsh-fs-observation-policy` also injects nothing).
|
|
21
21
|
|
|
22
|
+
## Permissions UI
|
|
23
|
+
|
|
24
|
+
Настройки → **Права** — read-only срез эффективных разрешений:
|
|
25
|
+
|
|
26
|
+
- текущий preset DSH (`current(session)`), sandbox и approval;
|
|
27
|
+
- каталог всех пресетов с пометкой текущего;
|
|
28
|
+
- жёсткие правила kit и защищённые пути;
|
|
29
|
+
- явное предупреждение: смена режима — только штатным picker DSH, страница ничего не переключает и не хранит approvals.
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
22
35
|
## Tool `guardrails`
|
|
23
36
|
|
|
24
37
|
Read-only. The canonical value is lossless JSON; the rendered text is a
|