dsh-desktop-kit 2.7.2 → 2.7.4
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 +11 -0
- package/README.md +3 -2
- package/client.js +160 -2
- package/docs/TEST-2.7.3.md +20 -0
- package/docs/TEST-2.7.4.md +19 -0
- package/package.json +1 -1
- package/plugins/dsh-agent-extensions/README.md +8 -0
- package/plugins/dsh-agent-extensions/client.js +166 -0
- package/plugins/dsh-agent-extensions/index.js +7 -0
- package/plugins/dsh-agent-extensions/lib/api.js +58 -0
- package/plugins/dsh-agent-extensions/package.json +11 -0
- package/plugins/dsh-agent-extensions/screenshots/agents-1440.png +0 -0
- package/plugins/dsh-agent-extensions/screenshots/agents-390.png +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.7.4 (2026-11-11)
|
|
4
|
+
|
|
5
|
+
- **Agents project detection.** Settings → Агенты берёт `path` текущего workspace через `useWorkspaces` и передаёт `cwd`; хост предпочитает `cwd → sessionId → живая сессия`. Исправлен показ `C:\Users\bulav` вместо папки проекта.
|
|
6
|
+
- **Agents API fallback.** Если sessionId не пришёл, используется cwd последней живой сессии, а не `process.cwd()`.
|
|
7
|
+
|
|
8
|
+
## 2.7.3 (2026-11-11)
|
|
9
|
+
|
|
10
|
+
- **Agents UI.** Настройки → **Агенты**: карточки `.dsh/agents/*.md`, enforced `permission`, tools и модель; 1440/390 скриншоты, аудит без overflow/markup.
|
|
11
|
+
- **Agents RPC.** `/api/dsh-agent-extensions/list` с привязкой к текущей сессии; `sessionId` → `cwd` → `.dsh/agents`.
|
|
12
|
+
- **Docs.** `docs/TEST-2.7.3.md`, README/plugin README со скриншотами.
|
|
13
|
+
|
|
3
14
|
## Unreleased
|
|
4
15
|
|
|
5
16
|
- **Permission bridge.** Guardrails читает effective `permissionPresets` и применяет Read Only / Workspace Write / Full к tools; kit-запреты (`rm -rf /`, prod DB, secrets) остаются в любом режиме. Approval — штатная DSH-карточка, без второго picker.
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
- **Approval** — штатная карточка DSH: Allow once / Always allow / Reject.
|
|
22
22
|
- **`.dsh/`** создаётся автоматически: agents, commands, rules, hooks, verify, mcp, PROJECT.md.
|
|
23
23
|
|
|
24
|
-
**v2.7.
|
|
24
|
+
**v2.7.4** · MIT · проверено на DSH `0.2.1-rc.5` и `0.2.2-rc.1` (Windows 11) · без внешних зависимостей и телеметрии
|
|
25
25
|
|
|
26
26
|
**План развития:** [docs/ROADMAP-3.0.md](docs/ROADMAP-3.0.md) — Memory Fabric, семантическое
|
|
27
27
|
слияние, локальный индекс памяти, шифрованные переносы, Memory Hub и npm trusted publishing.
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
## Что нового
|
|
36
36
|
|
|
37
37
|
- **Unreleased · auto-bootstrap.** При первом запуске kit сам создаёт `~/.dsh/mcp.json` и проектный каркас `.dsh/` (agents, commands, rules, hooks, verify, mcp, PROJECT.md). Trust-маркеры не создаются: hooks и MCP-серверы остаются явным решением пользователя.
|
|
38
|
+
- **Unreleased · Agents UI.** Настройки → **Агенты**: карточки `.dsh/agents/*.md`, enforced `permission`, tools и модель. Скриншоты 1440/390, без overflow и markup-ошибок.
|
|
38
39
|
- **Unreleased · permissions and subagents.** Guardrails читает effective `permissionPresets` и применяет Read Only / Workspace Write / Full к tools, сохраняя жёсткие kit-запреты. `project_agent` теперь реально ограничивает `tools`, `permission` и `model` ребёнка через pre-execute gate.
|
|
39
40
|
- **Unreleased · MCP manager.** 16-й плагин `dsh-mcp-manager`: stdio/HTTP MCP-серверы, `trusted`-гейт, tools `mcp__server__tool`, resources, `/mcp` и премиальный экран **Настройки → MCP** с пресетами GitHub/PostgreSQL/Filesystem, health, tools и секретами в `mcp.secrets.json` (0600). OAuth, prompts и sampling пока не реализованы.
|
|
40
41
|
- **Unreleased · agent extensions.** 15-й плагин `dsh-agent-extensions`: пользовательские hooks, кастомные агенты, проектные команды, `/init`, `/verify`, безопасные авто-чекпоинты и `/rewind`.
|
|
@@ -54,7 +55,7 @@
|
|
|
54
55
|
- **Память между машинами 2.3.0.** Восьмой плагин `dsh-memory-bridge` синхронизирует досье `dsh-project-memory` через **ваш приватный GitHub-репозиторий**: трёхстороннее слияние, конфликты с выбором версии, история и откат. Локальные досье остаются в `~/.dsh/projects-memory`, обмен — в `~/.dsh/memory-sync`; LLM и токены не используются.
|
|
55
56
|
- **Импорт памяти без дублей.** `dsh-project-memory` подхватывает досье, приехавшее с другой машины, и переселяет реестр на локальный путь вместо создания `<slug>-2`. `dsh-profile-transfer` сливает `registry.json`, не затирая проекты принимающей машины.
|
|
56
57
|
- **Обновления без потерь 2.2.0.** Навыки синхронизируются по манифесту SHA-256: неизменённые обновляются, ваши правки сохраняются и попадают в `/kit conflicts`; перед заменой — бэкап. Клиентские плагины изолированы: ошибка одной части не отключает остальные. Подробности — [docs/UPGRADING.md](docs/UPGRADING.md).
|
|
57
|
-
- **Проверено на DSH `0.2.2-rc.1` (win-x64).** Установка из репозитория, все
|
|
58
|
+
- **Проверено на DSH `0.2.2-rc.1` (win-x64).** Установка из репозитория, все 16 плагинов, 30 навыков, Skills Studio, Token Lab, Peak Guard, перенос чатов/профиля, локальная память и GitHub-обмен памятью. Найден и исправлен сдвиг формата `user/message` в 0.2.2, из-за которого автопилот молчал. Отчёт: [docs/TEST-0.2.2.md](docs/TEST-0.2.2.md).
|
|
58
59
|
- **Русский интерфейс 2.1.3.** Исправлены машинные переводы: «Этот повтор не удался» → «Не удалось выполнить запрос», ошибка потока DeepSeek, «Кэш попаданий», очередь сообщений и ещё 18 формулировок — в словаре и на живом экране.
|
|
59
60
|
- **Память проектов 2.1.** Проект распознаётся сам по рабочей папке, локальный автоскан (без LLM и сети) сразу даёт стек, команды, точки входа и тесты; дайджест сам подмешивает фрагменты досье, похожие на текущий запрос. Секреты и PII вычищаются до записи, файлы приватные (`0700/0600`). Новые действия `brief`/`scan` и команда `/memory`.
|
|
60
61
|
- **Навыки стали в 3–5 раз дешевле.** Ядро `content-writing` — 2,1k токенов вместо 11k, `seo` — 2,0k вместо 11k. Вся библиотека из 30 навыков — ≈42,6k вместо ≈60,6k, а грузятся только нужные ядра.
|
package/client.js
CHANGED
|
@@ -7236,7 +7236,165 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
7236
7236
|
return module.exports;
|
|
7237
7237
|
})());
|
|
7238
7238
|
|
|
7239
|
-
|
|
7239
|
+
/* ---- dsh-agent-extensions ---- */
|
|
7240
|
+
__parts.push((function () {
|
|
7241
|
+
var module = { exports: {} };
|
|
7242
|
+
var exports = module.exports;
|
|
7243
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7244
|
+
const React = require("react");
|
|
7245
|
+
const h = React.createElement;
|
|
7246
|
+
const useEffect = React.useEffect;
|
|
7247
|
+
const useState = React.useState;
|
|
7248
|
+
|
|
7249
|
+
const CSS = `
|
|
7250
|
+
.ag-root{--ag-line:var(--dsw-alias-border-l1,rgba(20,40,36,.13));--ag-soft:var(--dsw-alias-bg-layer-1,#f4f7f5);--ag-panel:var(--dsw-alias-bg-layer-2,#fff);--ag-text:var(--dsw-alias-label-primary,#17231f);--ag-muted:var(--dsw-alias-label-secondary,#61706b);--ag-accent:var(--dsw-alias-brand-primary,#0f766e);display:flex;flex-direction:column;gap:20px;max-width:1040px;min-width:0;color:var(--ag-text)}
|
|
7251
|
+
.ag-head{display:flex;align-items:flex-end;justify-content:space-between;gap:20px;flex-wrap:wrap}
|
|
7252
|
+
.ag-eyebrow{margin:0 0 6px;font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--ag-accent)}
|
|
7253
|
+
.ag-title{margin:0;font-size:clamp(24px,2.6vw,32px);line-height:1.12;letter-spacing:-.025em;font-weight:680}
|
|
7254
|
+
.ag-sub{margin:8px 0 0;max-width:66ch;color:var(--ag-muted);font-size:14px;line-height:1.5}
|
|
7255
|
+
.ag-stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
|
|
7256
|
+
.ag-stat{padding:14px 16px;border:1px solid var(--ag-line);border-radius:14px;background:linear-gradient(150deg,var(--ag-panel),var(--ag-soft));min-width:0}
|
|
7257
|
+
.ag-stat b{display:block;font-size:24px;line-height:1.1;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
|
|
7258
|
+
.ag-stat span{display:block;margin-top:5px;color:var(--ag-muted);font-size:11px;font-weight:650;letter-spacing:.08em;text-transform:uppercase}
|
|
7259
|
+
.ag-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
|
|
7260
|
+
.ag-card{display:flex;flex-direction:column;gap:12px;min-width:0;padding:16px;border:1px solid var(--ag-line);border-radius:16px;background:var(--ag-panel);box-shadow:0 1px 0 rgba(20,40,36,.03)}
|
|
7261
|
+
.ag-card-top{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;min-width:0}
|
|
7262
|
+
.ag-name{font-size:15px;font-weight:680;letter-spacing:-.01em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
7263
|
+
.ag-badge{flex:none;padding:3px 7px;border:1px solid var(--ag-line);border-radius:999px;font-size:10px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--ag-muted);white-space:nowrap}
|
|
7264
|
+
.ag-badge.read{color:#0f766e;border-color:rgba(15,118,110,.25);background:rgba(15,118,110,.07)}
|
|
7265
|
+
.ag-badge.write{color:#9a3412;border-color:rgba(154,52,18,.22);background:rgba(154,52,18,.06)}
|
|
7266
|
+
.ag-desc{margin:0;color:var(--ag-muted);font-size:12.5px;line-height:1.5;min-height:36px}
|
|
7267
|
+
.ag-meta{display:flex;gap:8px;flex-wrap:wrap;align-items:center;color:var(--ag-muted);font-size:11px}
|
|
7268
|
+
.ag-meta code{font:inherit;color:var(--ag-text);background:var(--ag-soft);border-radius:5px;padding:2px 6px;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
7269
|
+
.ag-tools{display:flex;gap:5px;flex-wrap:wrap;border-top:1px solid var(--ag-line);padding-top:10px;margin-top:auto}
|
|
7270
|
+
.ag-tool{padding:3px 7px;border-radius:6px;background:var(--ag-soft);color:var(--ag-muted);font-size:11px}
|
|
7271
|
+
.ag-empty{display:flex;flex-direction:column;align-items:flex-start;gap:12px;padding:26px;border:1px dashed var(--ag-line);border-radius:18px;background:linear-gradient(160deg,var(--ag-soft),transparent 75%)}
|
|
7272
|
+
.ag-empty h3{margin:0;font-size:18px;letter-spacing:-.015em}
|
|
7273
|
+
.ag-empty p{margin:0;max-width:62ch;color:var(--ag-muted);font-size:13px;line-height:1.5}
|
|
7274
|
+
.ag-code{width:100%;box-sizing:border-box;padding:12px;border:1px solid var(--ag-line);border-radius:10px;background:var(--ag-panel);color:var(--ag-text);font:12px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre;overflow:auto}
|
|
7275
|
+
.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}
|
|
7276
|
+
.ag-loading{color:var(--ag-muted);font-size:13px;padding:18px 0}
|
|
7277
|
+
.ag-note{margin:0;color:var(--ag-muted);font-size:11px;line-height:1.45}
|
|
7278
|
+
@media(max-width:860px){.ag-grid{grid-template-columns:1fr}.ag-stats{grid-template-columns:1fr 1fr}}
|
|
7279
|
+
@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}}
|
|
7280
|
+
@media(prefers-reduced-motion:reduce){.ag-card{transition:none}}
|
|
7281
|
+
`;
|
|
7282
|
+
|
|
7283
|
+
function fallbackError(value) {
|
|
7284
|
+
return value instanceof Error ? value.message : String(value);
|
|
7285
|
+
}
|
|
7286
|
+
|
|
7287
|
+
async function call(ctx, operation, payload, timeout = 30000) {
|
|
7288
|
+
let signal;
|
|
7289
|
+
try { signal = AbortSignal.timeout(timeout); } catch { signal = undefined; }
|
|
7290
|
+
const result = await ctx.connection.rpc.call("/api", `dsh-agent-extensions/${operation}`, payload ?? {}, signal);
|
|
7291
|
+
if (!result || result.ok !== true) throw new Error(result?.error?.message || "Ошибка RPC");
|
|
7292
|
+
return result.value;
|
|
7293
|
+
}
|
|
7294
|
+
|
|
7295
|
+
function AgentCard({ agent }) {
|
|
7296
|
+
const readOnly = agent.permission === "read-only";
|
|
7297
|
+
const tools = String(agent.tools || "").split(/[,\s]+/u).filter(Boolean);
|
|
7298
|
+
return h("article", { className: "ag-card" },
|
|
7299
|
+
h("div", { className: "ag-card-top" },
|
|
7300
|
+
h("span", { className: "ag-name", title: agent.name }, agent.name),
|
|
7301
|
+
h("span", { className: `ag-badge ${readOnly ? "read" : "write"}` }, readOnly ? "read-only" : agent.permission || "full"),
|
|
7302
|
+
),
|
|
7303
|
+
h("p", { className: "ag-desc" }, agent.description || "Без описания — добавьте `description` в frontmatter."),
|
|
7304
|
+
h("div", { className: "ag-meta" },
|
|
7305
|
+
h("span", null, "model"),
|
|
7306
|
+
h("code", null, agent.model || "inherit"),
|
|
7307
|
+
),
|
|
7308
|
+
h("div", { className: "ag-tools" },
|
|
7309
|
+
tools.length > 0 ? tools.map((tool) => h("span", { key: tool, className: "ag-tool" }, tool)) : h("span", { className: "ag-tool" }, "все tools"),
|
|
7310
|
+
),
|
|
7311
|
+
);
|
|
7312
|
+
}
|
|
7313
|
+
|
|
7314
|
+
function currentSessionSelector(state) {
|
|
7315
|
+
try {
|
|
7316
|
+
const sessions = Object.values(state.byId || {});
|
|
7317
|
+
const main = sessions.find((session) => session && session.retainedBy && (session.retainedBy.mainView || 0) > 0);
|
|
7318
|
+
if (main) return main.id;
|
|
7319
|
+
const ids = Array.isArray(state.ids) ? state.ids : [];
|
|
7320
|
+
return ids.length > 0 ? ids[ids.length - 1] : undefined;
|
|
7321
|
+
} catch { return undefined; }
|
|
7322
|
+
}
|
|
7323
|
+
|
|
7324
|
+
function AgentsSection(props) {
|
|
7325
|
+
const [state, setState] = useState({ loading: true, error: null, data: null });
|
|
7326
|
+
const useSessions = props && props.useSessions;
|
|
7327
|
+
const useWorkspaces = props && props.useWorkspaces;
|
|
7328
|
+
const sessionId = useSessions ? useSessions(currentSessionSelector) : undefined;
|
|
7329
|
+
const workspaceSnapshot = useWorkspaces ? useWorkspaces((snapshot) => snapshot) : undefined;
|
|
7330
|
+
const workspaces = Array.isArray(workspaceSnapshot?.items) ? workspaceSnapshot.items : [];
|
|
7331
|
+
const workspace = workspaces.find((item) => Array.isArray(item.sessionIds) && item.sessionIds.includes(sessionId))
|
|
7332
|
+
?? workspaces.find((item) => Array.isArray(item.sessionIds) && item.sessionIds.length > 0)
|
|
7333
|
+
?? workspaces[0];
|
|
7334
|
+
const cwd = typeof workspace?.path === "string" ? workspace.path : undefined;
|
|
7335
|
+
useEffect(() => {
|
|
7336
|
+
let cancelled = false;
|
|
7337
|
+
(async () => {
|
|
7338
|
+
try {
|
|
7339
|
+
const data = await call(window.__DSH_AGENTS_CTX__, "list", { sessionId, cwd });
|
|
7340
|
+
if (!cancelled) setState({ loading: false, error: null, data });
|
|
7341
|
+
} catch (error) {
|
|
7342
|
+
if (!cancelled) setState({ loading: false, error: fallbackError(error), data: null });
|
|
7343
|
+
}
|
|
7344
|
+
})();
|
|
7345
|
+
return () => { cancelled = true; };
|
|
7346
|
+
}, [sessionId, cwd]);
|
|
7347
|
+
const agents = state.data?.agents ?? [];
|
|
7348
|
+
const readOnly = agents.filter((agent) => agent.permission === "read-only").length;
|
|
7349
|
+
const models = new Set(agents.map((agent) => agent.model).filter(Boolean)).size;
|
|
7350
|
+
return h("section", { className: "ag-root", "aria-label": "Агенты проекта" },
|
|
7351
|
+
h("div", { className: "ag-head" },
|
|
7352
|
+
h("div", null,
|
|
7353
|
+
h("p", { className: "ag-eyebrow" }, "Agents · project roles"),
|
|
7354
|
+
h("h2", { className: "ag-title" }, "Агенты"),
|
|
7355
|
+
h("p", { className: "ag-sub" }, "Проектные роли из `.dsh/agents/*.md`. Их `tools`, `permission` и `model` применяются к дочернему агенту при запуске `project_agent`."),
|
|
7356
|
+
),
|
|
7357
|
+
),
|
|
7358
|
+
state.error ? h("div", { className: "ag-error", role: "alert" }, state.error) : null,
|
|
7359
|
+
h("div", { className: "ag-stats" },
|
|
7360
|
+
h("div", { className: "ag-stat" }, h("b", null, agents.length), h("span", null, "агентов")),
|
|
7361
|
+
h("div", { className: "ag-stat" }, h("b", null, readOnly), h("span", null, "read-only")),
|
|
7362
|
+
h("div", { className: "ag-stat" }, h("b", null, models), h("span", null, "моделей")),
|
|
7363
|
+
),
|
|
7364
|
+
state.loading ? h("div", { className: "ag-loading" }, "Загружаем агентов…") : null,
|
|
7365
|
+
!state.loading && agents.length === 0 ? h("div", { className: "ag-empty" },
|
|
7366
|
+
h("h3", null, "Пока нет проектных агентов"),
|
|
7367
|
+
h("p", null, "Создайте `.dsh/agents/reviewer.md` — и роль появится здесь. `tools` ограничивает инструменты, `permission: read-only` запрещает правки."),
|
|
7368
|
+
h("pre", { className: "ag-code" }, "---\nname: reviewer\ndescription: Ревью диффа\nmodel: deepseek-flash\ntools: read, grep\npermission: read-only\n---\n\nТы ревьюер. Верни только findings."),
|
|
7369
|
+
) : null,
|
|
7370
|
+
agents.length > 0 ? h("div", { className: "ag-grid" }, agents.map((agent) => h(AgentCard, { key: agent.name, agent }))) : null,
|
|
7371
|
+
h("p", { className: "ag-note" }, state.data?.project ? `Проект: ${state.data.project}` : "Проект определяется по текущей рабочей папке."),
|
|
7372
|
+
);
|
|
7373
|
+
}
|
|
7374
|
+
|
|
7375
|
+
function apply(ctx) {
|
|
7376
|
+
window.__DSH_AGENTS_CTX__ = ctx;
|
|
7377
|
+
ctx.effect(() => {
|
|
7378
|
+
const style = document.createElement("style");
|
|
7379
|
+
style.id = "dsh-agent-extensions";
|
|
7380
|
+
style.textContent = CSS;
|
|
7381
|
+
document.head.appendChild(style);
|
|
7382
|
+
return () => style.remove();
|
|
7383
|
+
}, "agent-extensions: styles");
|
|
7384
|
+
ctx.effect(() => ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
7385
|
+
name: "settings.section",
|
|
7386
|
+
id: "agent-extensions",
|
|
7387
|
+
order: 34,
|
|
7388
|
+
label: () => "Агенты",
|
|
7389
|
+
}, AgentsSection)), "agent-extensions: settings");
|
|
7390
|
+
}
|
|
7391
|
+
|
|
7392
|
+
exports.apply = apply;
|
|
7393
|
+
exports.inject = ["slots", "connection"];
|
|
7394
|
+
return module.exports;
|
|
7395
|
+
})());
|
|
7396
|
+
|
|
7397
|
+
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"];
|
|
7240
7398
|
const __kitErrors = [];
|
|
7241
7399
|
const __pushKitError = (label, error) => {
|
|
7242
7400
|
const message = String(error && error.message ? error.message : error);
|
|
@@ -7244,7 +7402,7 @@ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff
|
|
|
7244
7402
|
if (__kitErrors.length <= 5) console.error("[dsh-desktop-kit] registration failed:", label, message);
|
|
7245
7403
|
};
|
|
7246
7404
|
try {
|
|
7247
|
-
window.__DSH_DESKTOP_KIT__ = { version: "2.7.
|
|
7405
|
+
window.__DSH_DESKTOP_KIT__ = { version: "2.7.4", parts: __partNames, errors: __kitErrors, ready: false };
|
|
7248
7406
|
} catch {}
|
|
7249
7407
|
const __inject = [];
|
|
7250
7408
|
for (const __part of __parts) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# DSH Desktop Kit 2.7.3 — тест
|
|
2
|
+
|
|
3
|
+
Дата: 2026-11-11.
|
|
4
|
+
|
|
5
|
+
## Что проверено
|
|
6
|
+
|
|
7
|
+
- `npm test` — exit 0.
|
|
8
|
+
- Guardrails: 40 тестов, включая permission bridge.
|
|
9
|
+
- Agent extensions: allowlist и read-only enforcement, Agents UI.
|
|
10
|
+
- MCP manager: stdio, tools, resources, Settings RPC.
|
|
11
|
+
- `npm run build:client` — 8 клиентских частей.
|
|
12
|
+
- `npm run check:client` — OK.
|
|
13
|
+
- UI-аудит Agents UI: 390/768/1440 — overflow 0, console 0, markup 0.
|
|
14
|
+
- Release tarball содержит README, MCP screenshots и Agents screenshots.
|
|
15
|
+
|
|
16
|
+
## Ограничения
|
|
17
|
+
|
|
18
|
+
- MCP OAuth, prompts и sampling не реализованы.
|
|
19
|
+
- Worktrees и фоновые агенты не реализованы.
|
|
20
|
+
- Agents UI только читает `.dsh/agents`; запуск остаётся через `project_agent`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# DSH Desktop Kit 2.7.4 — тест
|
|
2
|
+
|
|
3
|
+
Дата: 2026-11-11.
|
|
4
|
+
|
|
5
|
+
## Что проверено
|
|
6
|
+
|
|
7
|
+
- `npm test` — exit 0.
|
|
8
|
+
- `npm run build:client` — OK; `check:client` — OK.
|
|
9
|
+
- Agents API: `cwd` из workspace, fallback на последнюю живую сессию.
|
|
10
|
+
- UI: Agents, MCP; скриншоты 390/1440.
|
|
11
|
+
|
|
12
|
+
## Исправление
|
|
13
|
+
|
|
14
|
+
`C:\Users\bulav` в футере Agents был вызван fallback-ом на `process.cwd()`.
|
|
15
|
+
Теперь клиент передаёт путь текущего workspace, а хост проверяет его первым.
|
|
16
|
+
|
|
17
|
+
## Ограничения
|
|
18
|
+
|
|
19
|
+
- MCP OAuth/prompts/sampling и worktrees не входят в 2.7.4.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-desktop-kit",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.4",
|
|
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",
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Claude Code-like project extensions for DSH, all opt-in and project-local.
|
|
4
4
|
|
|
5
|
+
## Settings → Агенты
|
|
6
|
+
|
|
7
|
+
Экран показывает все `.dsh/agents/*.md`: описание, enforced permission, tools и модель.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
<img src="screenshots/agents-390.png" alt="Агенты на 390px" width="390">
|
|
12
|
+
|
|
5
13
|
## Auto-bootstrap
|
|
6
14
|
|
|
7
15
|
On its first start the plugin creates:
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-agent-extensions — browser half.
|
|
3
|
+
*
|
|
4
|
+
* Settings → Agents: project-local roles, their enforced tools, permission
|
|
5
|
+
* level and model. The project path is taken from the current session when the
|
|
6
|
+
* host can resolve it; otherwise the kit falls back to the process cwd.
|
|
7
|
+
*/
|
|
8
|
+
window.__ModuleLoader__.load({
|
|
9
|
+
id: "dsh-agent-extensions",
|
|
10
|
+
factory: (require) => {
|
|
11
|
+
var module = { exports: {} };
|
|
12
|
+
var exports = module.exports;
|
|
13
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
14
|
+
const React = require("react");
|
|
15
|
+
const h = React.createElement;
|
|
16
|
+
const useEffect = React.useEffect;
|
|
17
|
+
const useState = React.useState;
|
|
18
|
+
|
|
19
|
+
const CSS = `
|
|
20
|
+
.ag-root{--ag-line:var(--dsw-alias-border-l1,rgba(20,40,36,.13));--ag-soft:var(--dsw-alias-bg-layer-1,#f4f7f5);--ag-panel:var(--dsw-alias-bg-layer-2,#fff);--ag-text:var(--dsw-alias-label-primary,#17231f);--ag-muted:var(--dsw-alias-label-secondary,#61706b);--ag-accent:var(--dsw-alias-brand-primary,#0f766e);display:flex;flex-direction:column;gap:20px;max-width:1040px;min-width:0;color:var(--ag-text)}
|
|
21
|
+
.ag-head{display:flex;align-items:flex-end;justify-content:space-between;gap:20px;flex-wrap:wrap}
|
|
22
|
+
.ag-eyebrow{margin:0 0 6px;font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--ag-accent)}
|
|
23
|
+
.ag-title{margin:0;font-size:clamp(24px,2.6vw,32px);line-height:1.12;letter-spacing:-.025em;font-weight:680}
|
|
24
|
+
.ag-sub{margin:8px 0 0;max-width:66ch;color:var(--ag-muted);font-size:14px;line-height:1.5}
|
|
25
|
+
.ag-stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
|
|
26
|
+
.ag-stat{padding:14px 16px;border:1px solid var(--ag-line);border-radius:14px;background:linear-gradient(150deg,var(--ag-panel),var(--ag-soft));min-width:0}
|
|
27
|
+
.ag-stat b{display:block;font-size:24px;line-height:1.1;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
|
|
28
|
+
.ag-stat span{display:block;margin-top:5px;color:var(--ag-muted);font-size:11px;font-weight:650;letter-spacing:.08em;text-transform:uppercase}
|
|
29
|
+
.ag-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
|
|
30
|
+
.ag-card{display:flex;flex-direction:column;gap:12px;min-width:0;padding:16px;border:1px solid var(--ag-line);border-radius:16px;background:var(--ag-panel);box-shadow:0 1px 0 rgba(20,40,36,.03)}
|
|
31
|
+
.ag-card-top{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;min-width:0}
|
|
32
|
+
.ag-name{font-size:15px;font-weight:680;letter-spacing:-.01em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
33
|
+
.ag-badge{flex:none;padding:3px 7px;border:1px solid var(--ag-line);border-radius:999px;font-size:10px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--ag-muted);white-space:nowrap}
|
|
34
|
+
.ag-badge.read{color:#0f766e;border-color:rgba(15,118,110,.25);background:rgba(15,118,110,.07)}
|
|
35
|
+
.ag-badge.write{color:#9a3412;border-color:rgba(154,52,18,.22);background:rgba(154,52,18,.06)}
|
|
36
|
+
.ag-desc{margin:0;color:var(--ag-muted);font-size:12.5px;line-height:1.5;min-height:36px}
|
|
37
|
+
.ag-meta{display:flex;gap:8px;flex-wrap:wrap;align-items:center;color:var(--ag-muted);font-size:11px}
|
|
38
|
+
.ag-meta code{font:inherit;color:var(--ag-text);background:var(--ag-soft);border-radius:5px;padding:2px 6px;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
39
|
+
.ag-tools{display:flex;gap:5px;flex-wrap:wrap;border-top:1px solid var(--ag-line);padding-top:10px;margin-top:auto}
|
|
40
|
+
.ag-tool{padding:3px 7px;border-radius:6px;background:var(--ag-soft);color:var(--ag-muted);font-size:11px}
|
|
41
|
+
.ag-empty{display:flex;flex-direction:column;align-items:flex-start;gap:12px;padding:26px;border:1px dashed var(--ag-line);border-radius:18px;background:linear-gradient(160deg,var(--ag-soft),transparent 75%)}
|
|
42
|
+
.ag-empty h3{margin:0;font-size:18px;letter-spacing:-.015em}
|
|
43
|
+
.ag-empty p{margin:0;max-width:62ch;color:var(--ag-muted);font-size:13px;line-height:1.5}
|
|
44
|
+
.ag-code{width:100%;box-sizing:border-box;padding:12px;border:1px solid var(--ag-line);border-radius:10px;background:var(--ag-panel);color:var(--ag-text);font:12px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre;overflow:auto}
|
|
45
|
+
.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}
|
|
46
|
+
.ag-loading{color:var(--ag-muted);font-size:13px;padding:18px 0}
|
|
47
|
+
.ag-note{margin:0;color:var(--ag-muted);font-size:11px;line-height:1.45}
|
|
48
|
+
@media(max-width:860px){.ag-grid{grid-template-columns:1fr}.ag-stats{grid-template-columns:1fr 1fr}}
|
|
49
|
+
@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}}
|
|
50
|
+
@media(prefers-reduced-motion:reduce){.ag-card{transition:none}}
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
function fallbackError(value) {
|
|
54
|
+
return value instanceof Error ? value.message : String(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function call(ctx, operation, payload, timeout = 30000) {
|
|
58
|
+
let signal;
|
|
59
|
+
try { signal = AbortSignal.timeout(timeout); } catch { signal = undefined; }
|
|
60
|
+
const result = await ctx.connection.rpc.call("/api", `dsh-agent-extensions/${operation}`, payload ?? {}, signal);
|
|
61
|
+
if (!result || result.ok !== true) throw new Error(result?.error?.message || "Ошибка RPC");
|
|
62
|
+
return result.value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function AgentCard({ agent }) {
|
|
66
|
+
const readOnly = agent.permission === "read-only";
|
|
67
|
+
const tools = String(agent.tools || "").split(/[,\s]+/u).filter(Boolean);
|
|
68
|
+
return h("article", { className: "ag-card" },
|
|
69
|
+
h("div", { className: "ag-card-top" },
|
|
70
|
+
h("span", { className: "ag-name", title: agent.name }, agent.name),
|
|
71
|
+
h("span", { className: `ag-badge ${readOnly ? "read" : "write"}` }, readOnly ? "read-only" : agent.permission || "full"),
|
|
72
|
+
),
|
|
73
|
+
h("p", { className: "ag-desc" }, agent.description || "Без описания — добавьте `description` в frontmatter."),
|
|
74
|
+
h("div", { className: "ag-meta" },
|
|
75
|
+
h("span", null, "model"),
|
|
76
|
+
h("code", null, agent.model || "inherit"),
|
|
77
|
+
),
|
|
78
|
+
h("div", { className: "ag-tools" },
|
|
79
|
+
tools.length > 0 ? tools.map((tool) => h("span", { key: tool, className: "ag-tool" }, tool)) : h("span", { className: "ag-tool" }, "все tools"),
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function currentSessionSelector(state) {
|
|
85
|
+
try {
|
|
86
|
+
const sessions = Object.values(state.byId || {});
|
|
87
|
+
const main = sessions.find((session) => session && session.retainedBy && (session.retainedBy.mainView || 0) > 0);
|
|
88
|
+
if (main) return main.id;
|
|
89
|
+
const ids = Array.isArray(state.ids) ? state.ids : [];
|
|
90
|
+
return ids.length > 0 ? ids[ids.length - 1] : undefined;
|
|
91
|
+
} catch { return undefined; }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function AgentsSection(props) {
|
|
95
|
+
const [state, setState] = useState({ loading: true, error: null, data: null });
|
|
96
|
+
const useSessions = props && props.useSessions;
|
|
97
|
+
const useWorkspaces = props && props.useWorkspaces;
|
|
98
|
+
const sessionId = useSessions ? useSessions(currentSessionSelector) : undefined;
|
|
99
|
+
const workspaceSnapshot = useWorkspaces ? useWorkspaces((snapshot) => snapshot) : undefined;
|
|
100
|
+
const workspaces = Array.isArray(workspaceSnapshot?.items) ? workspaceSnapshot.items : [];
|
|
101
|
+
const workspace = workspaces.find((item) => Array.isArray(item.sessionIds) && item.sessionIds.includes(sessionId))
|
|
102
|
+
?? workspaces.find((item) => Array.isArray(item.sessionIds) && item.sessionIds.length > 0)
|
|
103
|
+
?? workspaces[0];
|
|
104
|
+
const cwd = typeof workspace?.path === "string" ? workspace.path : undefined;
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
let cancelled = false;
|
|
107
|
+
(async () => {
|
|
108
|
+
try {
|
|
109
|
+
const data = await call(window.__DSH_AGENTS_CTX__, "list", { sessionId, cwd });
|
|
110
|
+
if (!cancelled) setState({ loading: false, error: null, data });
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (!cancelled) setState({ loading: false, error: fallbackError(error), data: null });
|
|
113
|
+
}
|
|
114
|
+
})();
|
|
115
|
+
return () => { cancelled = true; };
|
|
116
|
+
}, [sessionId, cwd]);
|
|
117
|
+
const agents = state.data?.agents ?? [];
|
|
118
|
+
const readOnly = agents.filter((agent) => agent.permission === "read-only").length;
|
|
119
|
+
const models = new Set(agents.map((agent) => agent.model).filter(Boolean)).size;
|
|
120
|
+
return h("section", { className: "ag-root", "aria-label": "Агенты проекта" },
|
|
121
|
+
h("div", { className: "ag-head" },
|
|
122
|
+
h("div", null,
|
|
123
|
+
h("p", { className: "ag-eyebrow" }, "Agents · project roles"),
|
|
124
|
+
h("h2", { className: "ag-title" }, "Агенты"),
|
|
125
|
+
h("p", { className: "ag-sub" }, "Проектные роли из `.dsh/agents/*.md`. Их `tools`, `permission` и `model` применяются к дочернему агенту при запуске `project_agent`."),
|
|
126
|
+
),
|
|
127
|
+
),
|
|
128
|
+
state.error ? h("div", { className: "ag-error", role: "alert" }, state.error) : null,
|
|
129
|
+
h("div", { className: "ag-stats" },
|
|
130
|
+
h("div", { className: "ag-stat" }, h("b", null, agents.length), h("span", null, "агентов")),
|
|
131
|
+
h("div", { className: "ag-stat" }, h("b", null, readOnly), h("span", null, "read-only")),
|
|
132
|
+
h("div", { className: "ag-stat" }, h("b", null, models), h("span", null, "моделей")),
|
|
133
|
+
),
|
|
134
|
+
state.loading ? h("div", { className: "ag-loading" }, "Загружаем агентов…") : null,
|
|
135
|
+
!state.loading && agents.length === 0 ? h("div", { className: "ag-empty" },
|
|
136
|
+
h("h3", null, "Пока нет проектных агентов"),
|
|
137
|
+
h("p", null, "Создайте `.dsh/agents/reviewer.md` — и роль появится здесь. `tools` ограничивает инструменты, `permission: read-only` запрещает правки."),
|
|
138
|
+
h("pre", { className: "ag-code" }, "---\nname: reviewer\ndescription: Ревью диффа\nmodel: deepseek-flash\ntools: read, grep\npermission: read-only\n---\n\nТы ревьюер. Верни только findings."),
|
|
139
|
+
) : null,
|
|
140
|
+
agents.length > 0 ? h("div", { className: "ag-grid" }, agents.map((agent) => h(AgentCard, { key: agent.name, agent }))) : null,
|
|
141
|
+
h("p", { className: "ag-note" }, state.data?.project ? `Проект: ${state.data.project}` : "Проект определяется по текущей рабочей папке."),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function apply(ctx) {
|
|
146
|
+
window.__DSH_AGENTS_CTX__ = ctx;
|
|
147
|
+
ctx.effect(() => {
|
|
148
|
+
const style = document.createElement("style");
|
|
149
|
+
style.id = "dsh-agent-extensions";
|
|
150
|
+
style.textContent = CSS;
|
|
151
|
+
document.head.appendChild(style);
|
|
152
|
+
return () => style.remove();
|
|
153
|
+
}, "agent-extensions: styles");
|
|
154
|
+
ctx.effect(() => ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
155
|
+
name: "settings.section",
|
|
156
|
+
id: "agent-extensions",
|
|
157
|
+
order: 34,
|
|
158
|
+
label: () => "Агенты",
|
|
159
|
+
}, AgentsSection)), "agent-extensions: settings");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
exports.apply = apply;
|
|
163
|
+
exports.inject = ["slots", "connection"];
|
|
164
|
+
return module.exports;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
@@ -14,6 +14,7 @@ import { createCheckpoint } from "./lib/checkpoints.js";
|
|
|
14
14
|
import { runProjectAgent } from "./lib/agents.js";
|
|
15
15
|
import { registerChildPolicyHook } from "./lib/policy.js";
|
|
16
16
|
import { registerCommands } from "./lib/commands.js";
|
|
17
|
+
import { registerAgentApi } from "./lib/api.js";
|
|
17
18
|
import { ensureGlobalScaffold, ensureProjectScaffold } from "./lib/scaffold.js";
|
|
18
19
|
import { findDshDir, loadAgents, projectFor } from "./lib/project.js";
|
|
19
20
|
|
|
@@ -135,6 +136,12 @@ export function apply(ctx) {
|
|
|
135
136
|
ctx.logger?.warn?.(`[agent-extensions] commands registration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
try {
|
|
140
|
+
ctx.inject(["connection"], (child) => registerAgentApi(child));
|
|
141
|
+
} catch (error) {
|
|
142
|
+
ctx.logger?.warn?.(`[agent-extensions] agents api skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
138
145
|
/* Auto-checkpoint before the first write in a session (at most once per 10 min). */
|
|
139
146
|
const lastAuto = new Map();
|
|
140
147
|
try {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authenticated RPC for the Agents settings page.
|
|
3
|
+
*/
|
|
4
|
+
import { loadAgents, projectFor } from "./project.js";
|
|
5
|
+
|
|
6
|
+
export const RPC_NAMESPACE = "dsh-agent-extensions";
|
|
7
|
+
const ROUTE_BASE = "/api/dsh-agent-extensions";
|
|
8
|
+
|
|
9
|
+
function ok(rpcId, value) {
|
|
10
|
+
return Response.json({ type: "server-response", rpcId, result: { ok: true, value } });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function fail(rpcId, message) {
|
|
14
|
+
return Response.json({ type: "server-response", rpcId, result: { ok: false, error: { code: "agents/bad-request", message, details: {} } } });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function envelope(request) {
|
|
18
|
+
const body = await request.json();
|
|
19
|
+
if (!body || body.type !== "client-request" || typeof body.rpcId !== "string" || typeof body.method !== "string") throw new Error("bad Connection envelope");
|
|
20
|
+
return { rpcId: body.rpcId, method: body.method, payload: body.payload ?? {} };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function registerAgentApi(ctx) {
|
|
24
|
+
const route = {
|
|
25
|
+
path: `${ROUTE_BASE}/list`,
|
|
26
|
+
methods: ["POST"],
|
|
27
|
+
requestBody: "buffered",
|
|
28
|
+
fetch: async (request) => {
|
|
29
|
+
let rpcId = "invalid-request";
|
|
30
|
+
try {
|
|
31
|
+
const value = await envelope(request);
|
|
32
|
+
rpcId = value.rpcId;
|
|
33
|
+
if (value.method !== `${RPC_NAMESPACE}/list`) throw new Error("endpoint/method mismatch");
|
|
34
|
+
const sessions = ctx.get?.("sessions");
|
|
35
|
+
let session = value.payload?.sessionId ? sessions?.get?.(value.payload.sessionId) : undefined;
|
|
36
|
+
if (!session && typeof sessions?.list === "function") {
|
|
37
|
+
const live = sessions.list();
|
|
38
|
+
session = live.length > 0 ? live[live.length - 1] : undefined;
|
|
39
|
+
}
|
|
40
|
+
const project = projectFor(value.payload?.cwd || session?.header?.cwd || process.cwd());
|
|
41
|
+
const agents = loadAgents(project.root, project.dsh).map((agent) => ({
|
|
42
|
+
name: agent.name,
|
|
43
|
+
description: agent.description,
|
|
44
|
+
model: agent.model,
|
|
45
|
+
tools: agent.tools,
|
|
46
|
+
permission: agent.permission,
|
|
47
|
+
}));
|
|
48
|
+
return ok(rpcId, { project: project.root, agents });
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return fail(rpcId, error instanceof Error ? error.message : String(error));
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
ctx.effect(() => {
|
|
55
|
+
const dispose = ctx.connection.fetch.register(route);
|
|
56
|
+
return () => { try { void Promise.resolve(dispose()).catch(() => {}); } catch { /* unloading */ } };
|
|
57
|
+
}, "agent-extensions: agents route");
|
|
58
|
+
}
|
|
@@ -4,6 +4,17 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"default": "./index.js"
|
|
10
|
+
},
|
|
11
|
+
"./client": {
|
|
12
|
+
"default": "./client.js"
|
|
13
|
+
},
|
|
14
|
+
"./package.json": {
|
|
15
|
+
"default": "./package.json"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
7
18
|
"description": "Project-local hooks, custom agents, commands and safe checkpoints for DeepSeek Harness — Claude Code-like extensibility.",
|
|
8
19
|
"license": "MIT"
|
|
9
20
|
}
|
|
Binary file
|
|
Binary file
|