dsh-desktop-kit 2.2.1 → 2.7.2
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 +80 -0
- package/README.md +113 -27
- package/client.js +3121 -32
- package/docs/AGENT-HARNESS.md +144 -0
- package/docs/ARCHITECTURE.md +38 -7
- package/docs/COMPATIBILITY.md +4 -2
- package/docs/DEVELOPMENT.md +56 -0
- package/docs/INSTALL.md +20 -11
- package/docs/LAPTOP-INSTALL.md +96 -0
- package/docs/ROADMAP-3.0.md +353 -0
- package/docs/ROADMAP.md +1 -1
- package/docs/SUBAGENTS.md +25 -0
- package/docs/TEST-0.2.2.md +5 -5
- package/docs/TEST-2.3.0.md +49 -0
- package/docs/TEST-2.7.2.md +46 -0
- package/docs/TOKENS.md +2 -2
- package/docs/TROUBLESHOOTING.md +27 -0
- package/docs/UI-QA.md +28 -0
- package/docs/images/peak-toast-peak.png +0 -0
- package/docs/images/peak-toast-soon.png +0 -0
- package/docs/preview/peak-toast.html +155 -0
- package/index.js +18 -1
- package/package.json +26 -6
- package/plugins/dsh-agent-extensions/README.md +99 -0
- package/plugins/dsh-agent-extensions/index.js +167 -0
- package/plugins/dsh-agent-extensions/lib/agents.js +76 -0
- package/plugins/dsh-agent-extensions/lib/checkpoints.js +103 -0
- package/plugins/dsh-agent-extensions/lib/commands.js +255 -0
- package/plugins/dsh-agent-extensions/lib/hooks.js +182 -0
- package/plugins/dsh-agent-extensions/lib/policy.js +84 -0
- package/plugins/dsh-agent-extensions/lib/project.js +144 -0
- package/plugins/dsh-agent-extensions/lib/scaffold.js +151 -0
- package/plugins/dsh-agent-extensions/package.json +9 -0
- package/plugins/dsh-browser-qa/README.md +221 -0
- package/plugins/dsh-browser-qa/cordis.patch.yml +4 -0
- package/plugins/dsh-browser-qa/index.js +165 -0
- package/plugins/dsh-browser-qa/lib/cdp.js +224 -0
- package/plugins/dsh-browser-qa/lib/chrome.js +466 -0
- package/plugins/dsh-browser-qa/lib/pages.js +552 -0
- package/plugins/dsh-browser-qa/lib/qa.js +827 -0
- package/plugins/dsh-browser-qa/lib/ui-gate.js +256 -0
- package/plugins/dsh-browser-qa/package.json +31 -0
- package/plugins/dsh-checkpoints/README.md +22 -0
- package/plugins/dsh-checkpoints/cordis.patch.yml +3 -0
- package/plugins/dsh-checkpoints/index.js +80 -0
- package/plugins/dsh-checkpoints/lib/checkpoints.js +111 -0
- package/plugins/dsh-checkpoints/package.json +25 -0
- package/plugins/dsh-github-review/README.md +131 -0
- package/plugins/dsh-github-review/cordis.patch.yml +3 -0
- package/plugins/dsh-github-review/index.js +536 -0
- package/plugins/dsh-github-review/lib/gh.js +494 -0
- package/plugins/dsh-github-review/lib/normalize.js +662 -0
- package/plugins/dsh-github-review/package.json +28 -0
- package/plugins/dsh-guardrails/README.md +264 -0
- package/plugins/dsh-guardrails/cordis.patch.yml +5 -0
- package/plugins/dsh-guardrails/index.js +389 -0
- package/plugins/dsh-guardrails/lib/hooks.js +262 -0
- package/plugins/dsh-guardrails/lib/modes.js +77 -0
- package/plugins/dsh-guardrails/lib/policy.js +592 -0
- package/plugins/dsh-guardrails/lib/scan.js +451 -0
- package/plugins/dsh-guardrails/lib/secrets.js +274 -0
- package/plugins/dsh-guardrails/package.json +26 -0
- package/plugins/dsh-mcp-manager/README.md +78 -0
- package/plugins/dsh-mcp-manager/client.js +373 -0
- package/plugins/dsh-mcp-manager/index.js +159 -0
- package/plugins/dsh-mcp-manager/lib/api.js +250 -0
- package/plugins/dsh-mcp-manager/lib/client.js +219 -0
- package/plugins/dsh-mcp-manager/lib/config.js +217 -0
- package/plugins/dsh-mcp-manager/lib/manager.js +146 -0
- package/plugins/dsh-mcp-manager/package.json +18 -0
- package/plugins/dsh-mcp-manager/screenshots/modal-390.png +0 -0
- package/plugins/dsh-mcp-manager/screenshots/settings-1440.png +0 -0
- package/plugins/dsh-mcp-manager/screenshots/settings-390.png +0 -0
- package/plugins/dsh-memory-bridge/README.md +161 -0
- package/plugins/dsh-memory-bridge/client.js +2264 -0
- package/plugins/dsh-memory-bridge/cordis.patch.yml +5 -0
- package/plugins/dsh-memory-bridge/docs/images/conflicts.png +0 -0
- package/plugins/dsh-memory-bridge/docs/images/hero.png +0 -0
- package/plugins/dsh-memory-bridge/docs/images/settings-dark.png +0 -0
- package/plugins/dsh-memory-bridge/docs/images/settings-light.png +0 -0
- package/plugins/dsh-memory-bridge/docs/images/settings-mobile.png +0 -0
- package/plugins/dsh-memory-bridge/docs/images/setup.png +0 -0
- package/plugins/dsh-memory-bridge/icon.svg +13 -0
- package/plugins/dsh-memory-bridge/index.js +378 -0
- package/plugins/dsh-memory-bridge/lib/bridge.js +636 -0
- package/plugins/dsh-memory-bridge/lib/github.js +473 -0
- package/plugins/dsh-memory-bridge/lib/merge.js +205 -0
- package/plugins/dsh-memory-bridge/lib/portable.js +314 -0
- package/plugins/dsh-memory-bridge/lib/state.js +565 -0
- package/plugins/dsh-memory-bridge/lib/sync.js +984 -0
- package/plugins/dsh-memory-bridge/locale/en.json +6 -0
- package/plugins/dsh-memory-bridge/locale/ru.json +6 -0
- package/plugins/dsh-memory-bridge/package.json +67 -0
- package/plugins/dsh-peak-guard/README.md +16 -3
- package/plugins/dsh-peak-guard/client.js +223 -8
- package/plugins/dsh-peak-guard/package.json +1 -1
- package/plugins/dsh-plan-mode/README.md +24 -0
- package/plugins/dsh-plan-mode/cordis.patch.yml +3 -0
- package/plugins/dsh-plan-mode/index.js +103 -0
- package/plugins/dsh-plan-mode/lib/plan.js +89 -0
- package/plugins/dsh-plan-mode/package.json +18 -0
- package/plugins/dsh-profile-transfer/client.js +277 -17
- package/plugins/dsh-profile-transfer/index.js +10 -0
- package/plugins/dsh-profile-transfer/package.json +3 -1
- package/plugins/dsh-profile-transfer/transfer.js +101 -2
- package/plugins/dsh-project-memory/index.js +92 -2
- package/plugins/dsh-project-memory/package.json +1 -1
- package/plugins/dsh-repo-map/README.md +26 -0
- package/plugins/dsh-repo-map/cordis.patch.yml +3 -0
- package/plugins/dsh-repo-map/index.js +99 -0
- package/plugins/dsh-repo-map/lib/repo-map.js +170 -0
- package/plugins/dsh-repo-map/package.json +25 -0
- package/plugins/dsh-skill-autopilot/index.js +135 -11
- package/skills/delivery-gate/SKILL.md +31 -0
- package/skills/git-and-ci/SKILL.md +5 -1
- package/skills/manifest.json +31 -6
- package/skills/requirements-and-planning/SKILL.md +3 -0
- package/skills/research-first/SKILL.md +35 -0
- package/skills/research-first/references/query-patterns.md +20 -0
- package/skills/subagent-playbook/SKILL.md +36 -0
- package/skills/subagent-playbook/references/prompts.md +39 -0
- package/skills/ui-craft/SKILL.md +43 -0
- package/skills/visual-qa/SKILL.md +29 -0
- package/skills/visual-qa/references/commands.md +32 -0
- package/skills-baseline.json +219 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="ru">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>dsh-peak-guard · пример уведомления</title>
|
|
6
|
+
<style>
|
|
7
|
+
:root{
|
|
8
|
+
--dsw-alias-bg-base:#fff;--dsw-alias-bg-layer-1:#fff;--dsw-alias-bg-layer-2:#f6f7f9;
|
|
9
|
+
--dsw-alias-label-primary:#14161a;--dsw-alias-label-secondary:#6b7280;
|
|
10
|
+
--dsw-alias-border-l1:rgba(20,22,26,.08);--dsw-alias-border-l2:rgba(20,22,26,.14);
|
|
11
|
+
--dsw-alias-state-error-primary:#d64545;--dsw-alias-state-warn-primary:#b45309;
|
|
12
|
+
--dsw-alias-brand-primary:#2f7df6;
|
|
13
|
+
}
|
|
14
|
+
*{box-sizing:border-box}
|
|
15
|
+
html,body{height:100%;margin:0}
|
|
16
|
+
body{font-family:"Segoe UI Variable","Segoe UI",system-ui,sans-serif;color:#14161a;background:#dfe3e8;overflow:hidden}
|
|
17
|
+
.mock-app{position:fixed;inset:0;display:flex;background:#f4f5f7}
|
|
18
|
+
.mock-sidebar{width:268px;flex:none;background:#f8f9fb;border-right:1px solid #e6e8ec;padding:14px 12px;display:flex;flex-direction:column;gap:8px}
|
|
19
|
+
.mock-brand{display:flex;align-items:center;gap:8px;padding:5px 7px 18px;font-weight:700;font-size:15px;letter-spacing:-.02em}
|
|
20
|
+
.mock-logo{width:19px;height:19px;border-radius:6px;background:#101216;color:#fff;display:flex;align-items:center;justify-content:center;font-size:11px}
|
|
21
|
+
.mock-tag{margin-left:auto;font-size:8px;letter-spacing:.1em;color:#8b919a;border:1px solid #d7dbe1;padding:2px 5px;border-radius:4px}
|
|
22
|
+
.mock-new{display:flex;align-items:center;gap:7px;background:#fff;border:1px solid #e2e5ea;border-radius:9px;padding:8px 10px;font-size:12px;color:#3a3f47;box-shadow:0 1px 2px rgba(20,22,26,.03)}
|
|
23
|
+
.mock-label{font-size:8.5px;letter-spacing:.12em;color:#9aa0a9;margin:12px 6px 2px;font-weight:700}
|
|
24
|
+
.mock-ws{display:flex;align-items:center;gap:7px;padding:7px 8px;border-radius:7px;font-size:11.5px;color:#4b515a}
|
|
25
|
+
.mock-ws.active{background:#eceef2;color:#14161a;font-weight:600}
|
|
26
|
+
.mock-dot{width:5px;height:5px;border-radius:50%;background:#8f959e;flex:none}
|
|
27
|
+
.mock-session{display:flex;gap:7px;align-items:center;margin-left:8px;padding:5px 8px;border-radius:6px;font-size:11px;color:#6b7280}
|
|
28
|
+
.mock-session.active{background:#e7eaf0;color:#14161a;font-weight:600}
|
|
29
|
+
.mock-session .mock-dot{background:#aab0b8}
|
|
30
|
+
.mock-session.active .mock-dot{background:#2f7df6}
|
|
31
|
+
.mock-bottom{margin-top:auto;border-top:1px solid #e6e8ec;padding-top:8px;display:flex;flex-direction:column;gap:6px}
|
|
32
|
+
.mock-tool{display:flex;align-items:center;gap:8px;font-size:11px;color:#6b7280;padding:5px 8px;border-radius:6px}
|
|
33
|
+
.mock-tools{margin-top:4px;background:#fff;border:1px solid #e6e8ec;border-radius:9px;padding:8px 9px;font-size:9.5px;line-height:15px;color:#7b818a}
|
|
34
|
+
.mock-tools b{display:block;color:#2e333a;font-size:10.5px;margin-bottom:2px}
|
|
35
|
+
.mock-main{flex:1;min-width:0;display:flex;flex-direction:column;background:#fff}
|
|
36
|
+
.mock-head{height:57px;flex:none;border-bottom:1px solid #eceef1;display:flex;align-items:center;gap:10px;padding:0 22px;font-size:12px;font-weight:650;color:#23262b}
|
|
37
|
+
.mock-tabs{display:flex;gap:16px;margin-left:16px;font-size:11px;font-weight:500;color:#8a9098;height:100%;align-items:center}
|
|
38
|
+
.mock-tab{padding:0 1px;height:100%;display:flex;align-items:center;border-bottom:2px solid transparent}
|
|
39
|
+
.mock-tab.active{color:#16181c;border-color:#16181c}
|
|
40
|
+
.mock-chat{flex:1;padding:24px 22px 150px;overflow:hidden;position:relative}
|
|
41
|
+
.mock-message{max-width:760px;margin:0 auto 14px;font-size:12px;line-height:20px;color:#3e444c}
|
|
42
|
+
.mock-user{background:#f1f3f6;border:1px solid #e7e9ee;border-radius:11px;padding:10px 13px}
|
|
43
|
+
.mock-assistant{padding:4px 2px 4px 13px;border-left:2px solid #e4e7ec}
|
|
44
|
+
.mock-line{height:8px;border-radius:4px;background:#eceef2;margin:7px 0}
|
|
45
|
+
.mock-line.w1{width:86%}.mock-line.w2{width:72%}.mock-line.w3{width:54%}.mock-line.dark{background:#dfe3e9}
|
|
46
|
+
.mock-composer{position:absolute;left:50%;bottom:20px;transform:translateX(-50%);width:min(850px,calc(100% - 44px));background:#fff;border:1px solid #dfe3e8;border-radius:13px;box-shadow:0 16px 40px -24px rgba(20,22,26,.28);padding:11px 13px;display:flex;align-items:center;gap:10px;font-size:11px;color:#9aa0a9}
|
|
47
|
+
.mock-send{margin-left:auto;width:26px;height:26px;border-radius:50%;background:#eef1f5;display:flex;align-items:center;justify-content:center;color:#8b9199}
|
|
48
|
+
.mock-caption{position:fixed;left:22px;top:18px;z-index:20;font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:#7c838d;background:rgba(255,255,255,.82);border:1px solid #e2e5ea;border-radius:999px;padding:6px 10px;backdrop-filter:blur(8px)}
|
|
49
|
+
.mock-caption b{color:#2c3138;letter-spacing:.02em;text-transform:none;font-size:11px;font-weight:600}
|
|
50
|
+
.mock-hint{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);z-index:20;font-size:9.5px;color:#a1a7b0}
|
|
51
|
+
.dpg-toast-details[hidden]{display:none}
|
|
52
|
+
.dpg-toast, .dpg-toast-fill{animation:none!important;transition:none!important}
|
|
53
|
+
.dpg-dot{width:6px;height:6px;border-radius:50%;background:var(--dpg-toast-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--dpg-toast-accent) 14%,transparent);animation:none}
|
|
54
|
+
.dpg-chip{display:inline-flex;align-items:center;height:22px;padding:0 8px;border-radius:6px;border:1px solid color-mix(in srgb,var(--dpg-toast-accent) 24%,transparent);background:color-mix(in srgb,var(--dpg-toast-accent) 7%,transparent);color:var(--dsw-alias-label-primary,#111);font-size:10.5px;font-weight:650;font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
55
|
+
/* corner toast: a quiet, premium alert over the whole frame */
|
|
56
|
+
.dpg-toast-wrap{position:fixed;right:22px;bottom:20px;z-index:1300;max-width:min(392px,calc(100vw - 24px));pointer-events:none}
|
|
57
|
+
.dpg-toast{--dpg-toast-accent:var(--dsw-alias-state-error-primary,#e5484d);position:relative;display:flex;flex-direction:column;gap:9px;padding:13px 13px 11px;border-radius:17px;border:1px solid color-mix(in srgb,var(--dpg-toast-accent) 24%,transparent);background:linear-gradient(155deg,color-mix(in srgb,var(--dpg-toast-accent) 6%,var(--dsw-alias-bg-layer-1,#fff)) 0%,var(--dsw-alias-bg-layer-1,#fff) 62%);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff 50%,transparent),0 22px 48px -28px color-mix(in srgb,var(--dpg-toast-accent) 78%,transparent);color:var(--dsw-alias-label-primary,#111);pointer-events:auto;animation:dpg-toast-in .32s cubic-bezier(.16,1,.3,1)}
|
|
58
|
+
.dpg-toast[data-mode="soon"]{--dpg-toast-accent:var(--dsw-alias-state-warn-primary,#d97706)}
|
|
59
|
+
.dpg-toast::before{content:"";position:absolute;left:0;top:14px;bottom:14px;width:3px;border-radius:0 3px 3px 0;background:var(--dpg-toast-accent)}
|
|
60
|
+
.dpg-toast-head{display:flex;align-items:flex-start;gap:10px;padding-left:5px}
|
|
61
|
+
.dpg-toast-icon{flex:none;width:34px;height:34px;border-radius:11px;display:flex;align-items:center;justify-content:center;color:#fff;background:linear-gradient(150deg,var(--dpg-toast-accent),color-mix(in srgb,var(--dpg-toast-accent) 55%,#4a070c));box-shadow:0 7px 16px -9px color-mix(in srgb,var(--dpg-toast-accent) 95%,transparent),inset 0 1px 0 rgba(255,255,255,.3)}
|
|
62
|
+
.dpg-toast[data-mode="soon"] .dpg-toast-icon{background:linear-gradient(150deg,var(--dpg-toast-accent),color-mix(in srgb,var(--dpg-toast-accent) 55%,#4a2c00));color:#fff}
|
|
63
|
+
.dpg-toast-copy{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1;padding-top:1px}
|
|
64
|
+
.dpg-toast-kicker{display:flex;align-items:center;gap:6px;font-size:9.5px;font-weight:750;letter-spacing:.14em;text-transform:uppercase;color:color-mix(in srgb,var(--dpg-toast-accent) 82%,#111)}
|
|
65
|
+
.dpg-toast-kicker .dpg-dot{width:5px;height:5px;box-shadow:0 0 0 3px color-mix(in srgb,var(--dpg-toast-accent) 13%,transparent)}
|
|
66
|
+
.dpg-toast-chip{display:inline-flex;align-items:center;height:15px;padding:0 5px;border-radius:4px;border:1px solid color-mix(in srgb,var(--dpg-toast-accent) 40%,transparent);background:color-mix(in srgb,var(--dpg-toast-accent) 9%,transparent);font-size:9px;font-weight:800;letter-spacing:.03em}
|
|
67
|
+
.dpg-toast-title{font-size:13.5px;line-height:18px;font-weight:660;letter-spacing:-.01em}
|
|
68
|
+
.dpg-toast-meta{font-size:11px;line-height:15px;color:var(--dsw-alias-label-secondary,#5b6472);font-variant-numeric:tabular-nums}
|
|
69
|
+
.dpg-toast-close{flex:none;border:none;background:transparent;color:var(--dsw-alias-label-secondary,#6b7280);width:24px;height:24px;border-radius:7px;display:flex;align-items:center;justify-content:center;font-size:15px;line-height:1;cursor:pointer;margin:-2px -2px 0 0}
|
|
70
|
+
.dpg-toast-close:hover{background:color-mix(in srgb,var(--dpg-toast-accent) 9%,transparent);color:var(--dpg-toast-accent)}
|
|
71
|
+
.dpg-toast-close:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2f7df6);outline-offset:1px}
|
|
72
|
+
.dpg-toast-track{position:relative;height:3px;border-radius:2px;background:color-mix(in srgb,var(--dpg-toast-accent) 13%,transparent);overflow:hidden;margin:0 1px}
|
|
73
|
+
.dpg-toast-fill{height:100%;border-radius:2px;background:linear-gradient(90deg,color-mix(in srgb,var(--dpg-toast-accent) 62%,#fff) 0%,var(--dpg-toast-accent) 100%);transition:width .9s linear}
|
|
74
|
+
.dpg-toast-actions{display:flex;align-items:center;gap:6px;padding-left:5px}
|
|
75
|
+
.dpg-toast-btn{border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.14));background:transparent;color:var(--dsw-alias-label-primary,#111);font:inherit;font-size:10.5px;font-weight:650;line-height:14px;padding:5px 9px;border-radius:7px;cursor:pointer;transition:background .16s ease,border-color .16s ease}
|
|
76
|
+
.dpg-toast-btn:hover{background:color-mix(in srgb,var(--dpg-toast-accent) 7%,transparent);border-color:color-mix(in srgb,var(--dpg-toast-accent) 34%,transparent)}
|
|
77
|
+
.dpg-toast-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2f7df6);outline-offset:1px}
|
|
78
|
+
.dpg-toast-btn[data-emphasis="true"]{border-color:transparent;background:var(--dpg-toast-accent);color:#fff}
|
|
79
|
+
.dpg-toast[data-mode="soon"] .dpg-toast-btn[data-emphasis="true"]{color:#2a1500}
|
|
80
|
+
.dpg-toast-details{display:grid;grid-template-columns:1fr;gap:5px;padding:7px 9px 2px;border-top:1px solid color-mix(in srgb,var(--dpg-toast-accent) 14%,transparent);font-size:10.5px;line-height:15px;color:var(--dsw-alias-label-secondary,#5b6472)}
|
|
81
|
+
.dpg-toast-details b{color:var(--dsw-alias-label-primary,#111);font-weight:650}
|
|
82
|
+
.dpg-toast-details .dpg-toast-schedule{display:flex;flex-wrap:wrap;gap:5px;margin-top:2px}
|
|
83
|
+
@keyframes dpg-toast-in{from{opacity:0;transform:translateY(10px) scale(.985)}to{opacity:1;transform:translateY(0) scale(1)}}
|
|
84
|
+
@media (max-width:520px){.dpg-toast-wrap{right:10px;left:10px;bottom:calc(10px + env(safe-area-inset-bottom,0px));max-width:none}.dpg-toast{max-height:70vh;overflow:auto;padding:12px 12px 10px}}
|
|
85
|
+
|
|
86
|
+
</style>
|
|
87
|
+
</head>
|
|
88
|
+
<body>
|
|
89
|
+
<div class="mock-app">
|
|
90
|
+
<aside class="mock-sidebar">
|
|
91
|
+
<div class="mock-brand"><span class="mock-logo">◈</span>deepseek<span class="mock-tag">HARNESS</span></div>
|
|
92
|
+
<div class="mock-new">+ Новая сессия</div>
|
|
93
|
+
<div class="mock-label">РАБОЧИЕ ПРОСТРАНСТВА</div>
|
|
94
|
+
<div class="mock-ws active"><span class="mock-dot" style="background:#6b7280"></span>Доработки</div>
|
|
95
|
+
<div class="mock-session active"><span class="mock-dot"></span>Доработка dsh-memory-bridge</div>
|
|
96
|
+
<div class="mock-session"><span class="mock-dot"></span>Nexum-core</div>
|
|
97
|
+
<div class="mock-session"><span class="mock-dot"></span>Доработка UI и шаблонов</div>
|
|
98
|
+
<div class="mock-bottom">
|
|
99
|
+
<div class="mock-tool">⚙ Плагины</div>
|
|
100
|
+
<div class="mock-tool">◷ Задачи автоматизации</div>
|
|
101
|
+
<div class="mock-tools"><b>НАВЫКИ · 25</b>40 запусков · 16 из 38k</div>
|
|
102
|
+
</div>
|
|
103
|
+
</aside>
|
|
104
|
+
<main class="mock-main">
|
|
105
|
+
<header class="mock-head">Доработка dsh-memory-bridge …<div class="mock-tabs"><span class="mock-tab active">Чат</span><span class="mock-tab">Траектория</span><span class="mock-tab">Статистика</span></div></header>
|
|
106
|
+
<section class="mock-chat">
|
|
107
|
+
<div class="mock-message mock-user">Сделай премиальное уведомление в углу о двойном тарифе.</div>
|
|
108
|
+
<div class="mock-message mock-assistant"><div class="mock-line dark w1"></div><div class="mock-line w2"></div><div class="mock-line w3"></div><div style="height:6px"></div><div class="mock-line w1"></div><div class="mock-line w2"></div></div>
|
|
109
|
+
<div class="mock-message mock-user" style="max-width:520px">Покажи, как это будет выглядеть в пик.</div>
|
|
110
|
+
<div class="mock-message mock-assistant"><div class="mock-line w1"></div><div class="mock-line w2"></div><div class="mock-line w3"></div></div>
|
|
111
|
+
<div class="mock-composer">+ <span>Сообщение или команда…</span><span class="mock-send">↑</span></div>
|
|
112
|
+
</section>
|
|
113
|
+
</main>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="dpg-toast-wrap">
|
|
116
|
+
<div class="dpg-toast" data-mode="peak" role="status" aria-live="polite" aria-atomic="true" aria-label="Предупреждение о пиковом тарифе">
|
|
117
|
+
<div class="dpg-toast-head">
|
|
118
|
+
<span class="dpg-toast-icon" aria-hidden="true"><svg viewBox="0 0 20 20" width="17" height="17" fill="none"><path d="M11.2 2.6 5.8 11h4.1l-1.1 6.4 5.4-8.4H10l1.2-6.4Z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/></svg></span>
|
|
119
|
+
<div class="dpg-toast-copy">
|
|
120
|
+
<div class="dpg-toast-kicker"><span class="dpg-dot"></span><span class="js-kicker">Пиковый тариф</span><span class="dpg-toast-chip">×2</span></div>
|
|
121
|
+
<div class="dpg-toast-title js-title">Токены сейчас стоят вдвое дороже</div>
|
|
122
|
+
<div class="dpg-toast-meta js-meta">осталось 2 ч 48 мин · 04:00–07:00 МСК</div>
|
|
123
|
+
</div>
|
|
124
|
+
<button class="dpg-toast-close" type="button" aria-label="Скрыть">×</button>
|
|
125
|
+
</div>
|
|
126
|
+
<div class="dpg-toast-track" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="38" aria-label="до конца пикового окна"><span class="dpg-toast-fill" style="display:block;width:38%"></span></div>
|
|
127
|
+
<div class="dpg-toast-details" hidden>
|
|
128
|
+
<span>В пиковые часы стоимость всех типов токенов для deepseek-flash и deepseek-v4-pro увеличивается вдвое. Вне этих часов действует скидка 50% (off-peak).</span>
|
|
129
|
+
<span class="dpg-toast-schedule"><b>Окна пика</b><span class="dpg-chip">04:00–07:00</span><span class="dpg-chip">09:00–13:00</span></span>
|
|
130
|
+
<span>Пиковые окна не действуют по выходным и в государственные праздники Китая (календарь 2026–2027 встроен); всё остальное время — off-peak.</span>
|
|
131
|
+
<span>Расписание проверено 2026-11-09</span>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="dpg-toast-actions">
|
|
134
|
+
<button class="dpg-toast-btn" type="button">Подробнее</button>
|
|
135
|
+
<button class="dpg-toast-btn" type="button" data-emphasis="true">Скрыть</button>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
<script>
|
|
140
|
+
const params = new URLSearchParams(location.search);
|
|
141
|
+
const mode = params.get("mode") === "soon" ? "soon" : "peak";
|
|
142
|
+
const details = params.get("details") === "1";
|
|
143
|
+
const toast = document.querySelector(".dpg-toast");
|
|
144
|
+
toast.dataset.mode = mode;
|
|
145
|
+
if (mode === "soon") {
|
|
146
|
+
document.querySelector(".js-kicker").textContent = "Скоро пик";
|
|
147
|
+
document.querySelector(".js-title").textContent = "Пиковый тариф через 4 мин";
|
|
148
|
+
document.querySelector(".js-meta").textContent = "начнётся через 4 мин · 04:00–07:00 МСК";
|
|
149
|
+
document.querySelector(".dpg-toast-fill").style.width = "18%";
|
|
150
|
+
document.querySelector(".dpg-toast-track").setAttribute("aria-valuenow", "18");
|
|
151
|
+
}
|
|
152
|
+
if (details) document.querySelector(".dpg-toast-details")?.removeAttribute("hidden");
|
|
153
|
+
</script>
|
|
154
|
+
</body>
|
|
155
|
+
</html>
|
package/index.js
CHANGED
|
@@ -28,8 +28,17 @@ import * as peakGuard from "./plugins/dsh-peak-guard/index.js";
|
|
|
28
28
|
import * as sessionMove from "./plugins/dsh-session-move/index.js";
|
|
29
29
|
import * as profileTransfer from "./plugins/dsh-profile-transfer/index.js";
|
|
30
30
|
import * as projectMemory from "./plugins/dsh-project-memory/index.js";
|
|
31
|
+
import * as memoryBridge from "./plugins/dsh-memory-bridge/index.js";
|
|
31
32
|
import * as tokenLab from "./plugins/dsh-token-lab/lib/index.js";
|
|
32
33
|
import * as skillAutopilot from "./plugins/dsh-skill-autopilot/index.js";
|
|
34
|
+
import * as repoMap from "./plugins/dsh-repo-map/index.js";
|
|
35
|
+
import * as checkpoints from "./plugins/dsh-checkpoints/index.js";
|
|
36
|
+
import * as planMode from "./plugins/dsh-plan-mode/index.js";
|
|
37
|
+
import * as browserQa from "./plugins/dsh-browser-qa/index.js";
|
|
38
|
+
import * as githubReview from "./plugins/dsh-github-review/index.js";
|
|
39
|
+
import * as guardrails from "./plugins/dsh-guardrails/index.js";
|
|
40
|
+
import * as agentExtensions from "./plugins/dsh-agent-extensions/index.js";
|
|
41
|
+
import * as mcpManager from "./plugins/dsh-mcp-manager/index.js";
|
|
33
42
|
|
|
34
43
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
35
44
|
const KIT_VERSION = (() => {
|
|
@@ -48,6 +57,15 @@ const parts = [
|
|
|
48
57
|
["dsh-session-move", sessionMove],
|
|
49
58
|
["dsh-profile-transfer", profileTransfer],
|
|
50
59
|
["dsh-project-memory", projectMemory],
|
|
60
|
+
["dsh-memory-bridge", memoryBridge],
|
|
61
|
+
["dsh-repo-map", repoMap],
|
|
62
|
+
["dsh-checkpoints", checkpoints],
|
|
63
|
+
["dsh-plan-mode", planMode],
|
|
64
|
+
["dsh-browser-qa", browserQa],
|
|
65
|
+
["dsh-github-review", githubReview],
|
|
66
|
+
["dsh-guardrails", guardrails],
|
|
67
|
+
["dsh-agent-extensions", agentExtensions],
|
|
68
|
+
["dsh-mcp-manager", mcpManager],
|
|
51
69
|
];
|
|
52
70
|
|
|
53
71
|
/** The kit itself needs no service; each child declares its own. */
|
|
@@ -185,4 +203,3 @@ export function apply(ctx) {
|
|
|
185
203
|
ctx.logger?.debug?.(`[dsh-desktop-kit] commands service unavailable: ${String(error)}`);
|
|
186
204
|
}
|
|
187
205
|
}
|
|
188
|
-
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-desktop-kit",
|
|
3
|
-
"version": "2.2
|
|
4
|
-
"description": "DeepSeek Harness desktop kit 2.
|
|
3
|
+
"version": "2.7.2",
|
|
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",
|
|
7
7
|
"dsh",
|
|
@@ -12,10 +12,28 @@
|
|
|
12
12
|
"tokens",
|
|
13
13
|
"context",
|
|
14
14
|
"skills-studio",
|
|
15
|
-
"token-lab"
|
|
15
|
+
"token-lab",
|
|
16
|
+
"memory-bridge",
|
|
17
|
+
"project-memory",
|
|
18
|
+
"github-sync",
|
|
19
|
+
"guardrails",
|
|
20
|
+
"repo-map",
|
|
21
|
+
"checkpoints",
|
|
22
|
+
"plan-mode",
|
|
23
|
+
"browser-qa",
|
|
24
|
+
"github-review",
|
|
25
|
+
"usb"
|
|
16
26
|
],
|
|
17
27
|
"license": "MIT",
|
|
18
28
|
"author": "DSH Desktop Kit contributors",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/Bulavackii/dsh-desktop-kit.git"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/Bulavackii/dsh-desktop-kit#readme",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/Bulavackii/dsh-desktop-kit/issues"
|
|
36
|
+
},
|
|
19
37
|
"type": "module",
|
|
20
38
|
"main": "index.js",
|
|
21
39
|
"exports": {
|
|
@@ -49,15 +67,17 @@
|
|
|
49
67
|
"plugins/*/README.md",
|
|
50
68
|
"plugins/*/screenshots/**",
|
|
51
69
|
"skills/**",
|
|
52
|
-
"tools/skill-cost.mjs"
|
|
70
|
+
"tools/skill-cost.mjs",
|
|
71
|
+
"plugins/*/docs/images/**"
|
|
53
72
|
],
|
|
54
73
|
"scripts": {
|
|
55
74
|
"build:client": "node tools/build-client.mjs",
|
|
56
75
|
"build:skills": "node tools/build-skill-manifest.mjs",
|
|
57
76
|
"check:client": "node tools/check-client.mjs client.js",
|
|
58
|
-
"test": "node tools/smoke-tests.mjs && node tools/memory-tests.mjs && node tools/skills-sync-tests.mjs && node tools/kit-upgrade-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 plugins/dsh-guardrails/tools/guardrails-tests.mjs",
|
|
59
78
|
"skills:cost": "node tools/skill-cost.mjs",
|
|
60
|
-
"skills:check": "node tools/skill-cost.mjs --check"
|
|
79
|
+
"skills:check": "node tools/skill-cost.mjs --check",
|
|
80
|
+
"release": "node tools/release.mjs"
|
|
61
81
|
},
|
|
62
82
|
"engines": {
|
|
63
83
|
"node": ">=20"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# dsh-agent-extensions
|
|
2
|
+
|
|
3
|
+
Claude Code-like project extensions for DSH, all opt-in and project-local.
|
|
4
|
+
|
|
5
|
+
## Auto-bootstrap
|
|
6
|
+
|
|
7
|
+
On its first start the plugin creates:
|
|
8
|
+
|
|
9
|
+
- `~/.dsh/mcp.json` (empty, valid) and `~/.dsh/mcp.README.md`;
|
|
10
|
+
- for each project session: `.dsh/agents/`, `.dsh/commands/`, `.dsh/rules/`, `.dsh/hooks.json`, `.dsh/verify.json`, `.dsh/mcp.json`, `.dsh/PROJECT.md`, `.dsh/README.md`, `.dsh/.gitignore`, `.dsh/.kit-scaffold.json`.
|
|
11
|
+
|
|
12
|
+
Scaffolding is idempotent and never overwrites existing files. It **does not** create `hooks.trusted` or `mcp.trusted`: running project hooks/servers stays an explicit decision.
|
|
13
|
+
|
|
14
|
+
## Hooks — `.dsh/hooks.json`
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"hooks": {
|
|
19
|
+
"PreToolUse": [{ "matcher": "write|edit", "command": "node .dsh/hooks/pre-tool.mjs", "timeoutMs": 30000 }],
|
|
20
|
+
"PostToolUse": [{ "matcher": "write|edit", "command": "npm run lint -- --fix" }],
|
|
21
|
+
"Stop": [{ "command": "node .dsh/hooks/stop.mjs" }],
|
|
22
|
+
"FileChanged": [{ "matcher": "\\.(html|css|tsx)$", "command": "npm run check:css" }],
|
|
23
|
+
"SessionStart": [{ "command": "node .dsh/hooks/session.mjs" }]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Hooks do not run until you trust them:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
/hooks trust
|
|
32
|
+
/hooks off
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- `PreToolUse` exit **2** blocks the tool call; other non-zero codes are reported but do not block.
|
|
36
|
+
- `Stop` exit **2** steers the agent with the hook output instead of closing the turn.
|
|
37
|
+
- `FileChanged` runs before a write/edit and can block with exit 2.
|
|
38
|
+
- The hook receives JSON on stdin: event, tool/file, arguments, project and session id.
|
|
39
|
+
- Commands run with the project cwd, `DSH_PROJECT_DIR`, `DSH_SESSION_ID`, a timeout (max 120 s) and bounded output.
|
|
40
|
+
|
|
41
|
+
## Custom agents — `.dsh/agents/<name>.md`
|
|
42
|
+
|
|
43
|
+
```md
|
|
44
|
+
---
|
|
45
|
+
name: reviewer
|
|
46
|
+
description: Ревью диффа, тестов и рисков
|
|
47
|
+
model: deepseek-flash
|
|
48
|
+
tools: read, grep, bash
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
Ты ревьюер. Проверяй дифф, тесты, права и секреты. Верни только findings.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Use:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
project_agent action=list
|
|
58
|
+
project_agent action=run name=reviewer task="Проверь текущий дифф"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The child runs in its own context through the DSH `subagents` service and returns only its final text.
|
|
62
|
+
|
|
63
|
+
`tools`, `permission` and `model` are enforced, not just documentation:
|
|
64
|
+
|
|
65
|
+
- `tools: read, grep` — the child receives a deny for any other tool;
|
|
66
|
+
- `permission: read-only` — write/edit/apply_patch and mutating shell commands are denied;
|
|
67
|
+
- `permission: workspace-write` — writes are allowed, subject to the parent's guardrails;
|
|
68
|
+
- `model` — passed as the child's model option.
|
|
69
|
+
|
|
70
|
+
Enforcement is a global `tools/pre-execute` gate keyed by the reserved child
|
|
71
|
+
session id, so the child cannot escape it by calling another tool name.
|
|
72
|
+
|
|
73
|
+
## Commands
|
|
74
|
+
|
|
75
|
+
Built in:
|
|
76
|
+
|
|
77
|
+
| Command | What it does |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `/init` | creates `.dsh/PROJECT.md` and `.dsh/rules/project.md` |
|
|
80
|
+
| `/doctor` | hooks, agents, commands, trust and checkpoints |
|
|
81
|
+
| `/verify` | runs `.dsh/verify.json` commands or `npm test` |
|
|
82
|
+
| `/hooks status\|trust\|off` | manage hook trust |
|
|
83
|
+
| `/checkpoint [label]` | safe git checkpoint |
|
|
84
|
+
| `/rewind [id]` | list or restore checkpoints |
|
|
85
|
+
| `/project list\|<name> [args]` | project commands from `.dsh/commands/*.md` |
|
|
86
|
+
|
|
87
|
+
Project commands are intentionally behind `/project`: DSH command names are process-wide, so a direct `/deploy` alias would bind to whichever workspace was first. `.dsh/commands/deploy.md` becomes `/project deploy`.
|
|
88
|
+
|
|
89
|
+
## Checkpoints
|
|
90
|
+
|
|
91
|
+
`/checkpoint` creates an unreachable tree under `refs/dsh-agent-extensions/<id>` using a **temporary git index**. Your staged changes and HEAD are not touched. `/rewind <id>` restores tracked files from that tree with `git restore --source=... --worktree`, so files created after the checkpoint are not deleted.
|
|
92
|
+
|
|
93
|
+
Auto-checkpoint runs before the first write in a session, at most once per 10 minutes, and skips `.dsh`, `node_modules`, `dist`, `build` and `coverage`.
|
|
94
|
+
|
|
95
|
+
## Limits
|
|
96
|
+
|
|
97
|
+
- Hooks are arbitrary process execution: trust only repositories you control.
|
|
98
|
+
- Subagent model/tool restrictions depend on the DSH provider; `tools` is not enforced here.
|
|
99
|
+
- MCP manager is not included: the current profile has no MCP service/package to attach it to. Add it when an MCP host plugin is installed.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-agent-extensions — Claude-Code-like project extensions for DSH.
|
|
3
|
+
*
|
|
4
|
+
* Adds four project-local surfaces:
|
|
5
|
+
* - `.dsh/hooks.json` — trusted lifecycle hooks;
|
|
6
|
+
* - `.dsh/agents/*.md` — custom subagents via the `project_agent` tool;
|
|
7
|
+
* - `.dsh/commands/*.md` — project slash commands;
|
|
8
|
+
* - safe git checkpoints + `/rewind`.
|
|
9
|
+
*/
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { registerProjectHooks } from "./lib/hooks.js";
|
|
13
|
+
import { createCheckpoint } from "./lib/checkpoints.js";
|
|
14
|
+
import { runProjectAgent } from "./lib/agents.js";
|
|
15
|
+
import { registerChildPolicyHook } from "./lib/policy.js";
|
|
16
|
+
import { registerCommands } from "./lib/commands.js";
|
|
17
|
+
import { ensureGlobalScaffold, ensureProjectScaffold } from "./lib/scaffold.js";
|
|
18
|
+
import { findDshDir, loadAgents, projectFor } from "./lib/project.js";
|
|
19
|
+
|
|
20
|
+
export const inject = ["tools", "systemPrompt"];
|
|
21
|
+
|
|
22
|
+
const TOOL = "project_agent";
|
|
23
|
+
const AUTO_CHECKPOINT_MS = 10 * 60_000;
|
|
24
|
+
|
|
25
|
+
function projectFromAgent(agent) {
|
|
26
|
+
const cwd = agent?.session?.header?.cwd;
|
|
27
|
+
return projectFor(cwd);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function toolDefinition(ctx) {
|
|
31
|
+
return {
|
|
32
|
+
name: TOOL,
|
|
33
|
+
description:
|
|
34
|
+
"Run or list project-local custom agents from `.dsh/agents/*.md`. `list` shows names/descriptions/models; `run` starts a child agent with its own context and returns only its final text. Use for repeated specialised work (reviewer, researcher, SEO, migration).",
|
|
35
|
+
parameters: {
|
|
36
|
+
type: "object",
|
|
37
|
+
additionalProperties: false,
|
|
38
|
+
properties: {
|
|
39
|
+
action: { type: "string", enum: ["list", "run"], description: "list project agents or run one by name" },
|
|
40
|
+
name: { type: "string", description: "Agent name from `.dsh/agents/<name>.md`" },
|
|
41
|
+
task: { type: "string", description: "Task/prompt for the child agent (required for run)" },
|
|
42
|
+
},
|
|
43
|
+
required: ["action"],
|
|
44
|
+
},
|
|
45
|
+
async execute(input, exec) {
|
|
46
|
+
const project = projectFromAgent(exec?.agent);
|
|
47
|
+
const agents = loadAgents(project.root, project.dsh ?? path.join(project.root, ".dsh"));
|
|
48
|
+
if (input?.action === "list") {
|
|
49
|
+
const text = agents.length
|
|
50
|
+
? agents.map((agent) => `- ${agent.name}${agent.model ? ` · ${agent.model}` : ""}${agent.description ? ` — ${agent.description}` : ""}`).join("\n")
|
|
51
|
+
: "Нет проектных агентов. Создайте `.dsh/agents/<name>.md`.";
|
|
52
|
+
return { ok: true, text: `project_agent list:\n${text}` };
|
|
53
|
+
}
|
|
54
|
+
if (input?.action !== "run") return { ok: false, text: "project_agent: action must be list or run" };
|
|
55
|
+
const name = String(input?.name ?? "").trim();
|
|
56
|
+
const task = String(input?.task ?? "").trim();
|
|
57
|
+
if (!name || !task) return { ok: false, text: "project_agent: name and task are required for run" };
|
|
58
|
+
const definition = agents.find((agent) => agent.name === name);
|
|
59
|
+
if (!definition) return { ok: false, text: `project_agent: unknown agent "${name}"` };
|
|
60
|
+
try {
|
|
61
|
+
const result = await runProjectAgent(ctx, exec, definition, task);
|
|
62
|
+
return { ok: true, text: `project_agent ${name} (${result.stopReason}):\n${result.text}` };
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return { ok: false, text: `project_agent ${name}: ${error instanceof Error ? error.message : String(error)}` };
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function apply(ctx) {
|
|
71
|
+
try {
|
|
72
|
+
const scaffold = ensureGlobalScaffold();
|
|
73
|
+
if (scaffold.created.length > 0) ctx.logger?.info?.(`[agent-extensions] scaffolded global: ${scaffold.created.join(", ")}`);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
ctx.logger?.warn?.(`[agent-extensions] global scaffold skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
76
|
+
}
|
|
77
|
+
const hooks = registerProjectHooks(ctx);
|
|
78
|
+
ctx.effect(() => () => hooks.dispose(), "agent-extensions: project hooks");
|
|
79
|
+
const childPolicy = registerChildPolicyHook(ctx);
|
|
80
|
+
ctx.effect(() => () => childPolicy.dispose(), "agent-extensions: child policy");
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
ctx.effect(
|
|
84
|
+
() =>
|
|
85
|
+
ctx.on("agent/created", (payload) => {
|
|
86
|
+
try {
|
|
87
|
+
const cwd = payload?.agent?.session?.header?.cwd;
|
|
88
|
+
const result = ensureProjectScaffold(cwd);
|
|
89
|
+
if (!result.skipped && result.created.length > 0) {
|
|
90
|
+
ctx.logger?.info?.(`[agent-extensions] scaffolded project ${cwd}: ${result.created.length} paths`);
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
ctx.logger?.warn?.(`[agent-extensions] project scaffold skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
94
|
+
}
|
|
95
|
+
}),
|
|
96
|
+
"agent-extensions: project scaffold",
|
|
97
|
+
);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
ctx.logger?.warn?.(`[agent-extensions] project scaffold hook skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
ctx.effect(() => ctx.tools.register(toolDefinition(ctx)), "agent-extensions: project_agent tool");
|
|
104
|
+
} catch (error) {
|
|
105
|
+
ctx.logger?.warn?.(`[agent-extensions] tool registration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
ctx.systemPrompt.context({
|
|
110
|
+
name: "dsh-agent-extensions: project agents",
|
|
111
|
+
order: 134,
|
|
112
|
+
interpolate: false,
|
|
113
|
+
required: false,
|
|
114
|
+
text: (assemblyContext) => {
|
|
115
|
+
try {
|
|
116
|
+
const session = assemblyContext?.agent?.session;
|
|
117
|
+
if (!session) return "";
|
|
118
|
+
const project = projectFromAgent(assemblyContext.agent);
|
|
119
|
+
const agents = loadAgents(project.root, project.dsh ?? path.join(project.root, ".dsh"));
|
|
120
|
+
if (agents.length === 0) return "";
|
|
121
|
+
const lines = agents.slice(0, 20).map((agent) => `- ${agent.name}: ${agent.description || "project agent"}${agent.model ? ` (${agent.model})` : ""}`);
|
|
122
|
+
return `<project-agents>\n${lines.join("\n")}\nЗапускай через tool project_agent action=run name=<name> task=<...>.\n</project-agents>`;
|
|
123
|
+
} catch {
|
|
124
|
+
return "";
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
} catch (error) {
|
|
129
|
+
ctx.logger?.warn?.(`[agent-extensions] system prompt registration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
ctx.inject(["commands"], (child) => registerCommands(child));
|
|
134
|
+
} catch (error) {
|
|
135
|
+
ctx.logger?.warn?.(`[agent-extensions] commands registration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Auto-checkpoint before the first write in a session (at most once per 10 min). */
|
|
139
|
+
const lastAuto = new Map();
|
|
140
|
+
try {
|
|
141
|
+
ctx.effect(
|
|
142
|
+
() =>
|
|
143
|
+
ctx.on("fs/write-intent", async (target, actor, next) => {
|
|
144
|
+
const file = target?.displayPath ?? target?.path ?? "";
|
|
145
|
+
const sessionId = String(actor?.id ?? ctx.get("agents")?.currentInitiator?.()?.id ?? "");
|
|
146
|
+
if (!sessionId || !/\.(?:js|mjs|cjs|ts|tsx|jsx|css|scss|html|vue|svelte|astro|md|json|yml|yaml)$/iu.test(file)) return next();
|
|
147
|
+
if (/(?:^|[\\/])(?:node_modules|\.git|\.dsh|dist|build|coverage)(?:[\\/]|$)/iu.test(file)) return next();
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
if (now - (lastAuto.get(sessionId) ?? 0) < AUTO_CHECKPOINT_MS) return next();
|
|
150
|
+
lastAuto.set(sessionId, now);
|
|
151
|
+
try {
|
|
152
|
+
const project = findDshDir(path.dirname(file));
|
|
153
|
+
const meta = createCheckpoint({ cwd: project.root, label: "auto before write" });
|
|
154
|
+
ctx.logger?.debug?.(`[agent-extensions] auto checkpoint ${meta.id}`);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
ctx.logger?.debug?.(`[agent-extensions] auto checkpoint skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
157
|
+
}
|
|
158
|
+
return next();
|
|
159
|
+
}),
|
|
160
|
+
"agent-extensions: auto checkpoint",
|
|
161
|
+
);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
ctx.logger?.warn?.(`[agent-extensions] auto checkpoint hook skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
ctx.logger?.info?.("[agent-extensions] ready");
|
|
167
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project-local custom agents: `.dsh/agents/<name>.md`.
|
|
3
|
+
*
|
|
4
|
+
* The file frontmatter describes when to use the agent; the body becomes the
|
|
5
|
+
* child prompt. A child runs through the DSH subagents service with its own
|
|
6
|
+
* context and returns only its final text.
|
|
7
|
+
*/
|
|
8
|
+
import crypto from "node:crypto";
|
|
9
|
+
import { loadAgents } from "./project.js";
|
|
10
|
+
import { clearChildPolicy, setChildPolicy } from "./policy.js";
|
|
11
|
+
|
|
12
|
+
function textFromOutput(output) {
|
|
13
|
+
if (!Array.isArray(output)) return "";
|
|
14
|
+
return output
|
|
15
|
+
.filter((block) => block && block.type === "text" && typeof block.text === "string")
|
|
16
|
+
.map((block) => block.text)
|
|
17
|
+
.join("\n")
|
|
18
|
+
.trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function runProjectAgent(ctx, exec, definition, task) {
|
|
22
|
+
const subagents = ctx.get("subagents");
|
|
23
|
+
if (!subagents || typeof subagents.startActivation !== "function") {
|
|
24
|
+
throw new Error("subagents service is unavailable");
|
|
25
|
+
}
|
|
26
|
+
const providers = typeof subagents.list === "function" ? subagents.list() : [];
|
|
27
|
+
const provider = providers.includes("spawn") ? "spawn" : providers[0];
|
|
28
|
+
if (!provider) throw new Error("no subagent provider is registered");
|
|
29
|
+
const controller = new AbortController();
|
|
30
|
+
const signal = exec?.signal;
|
|
31
|
+
const abort = () => controller.abort(signal?.reason ?? new Error("parent aborted"));
|
|
32
|
+
if (signal?.aborted) abort();
|
|
33
|
+
else signal?.addEventListener?.("abort", abort, { once: true });
|
|
34
|
+
const reservedId = crypto.randomUUID();
|
|
35
|
+
let activeId = reservedId;
|
|
36
|
+
setChildPolicy(reservedId, definition);
|
|
37
|
+
try {
|
|
38
|
+
const prompt = [`<project-agent name="${definition.name}">`, definition.body, "</project-agent>", "", "Task:", String(task ?? "")].join("\n");
|
|
39
|
+
const activation = await subagents.startActivation({
|
|
40
|
+
provider,
|
|
41
|
+
label: `agent:${definition.name}`,
|
|
42
|
+
childId: reservedId,
|
|
43
|
+
request: {
|
|
44
|
+
prompt: [{ type: "text", text: prompt }],
|
|
45
|
+
parent: exec.agent,
|
|
46
|
+
signal: controller.signal,
|
|
47
|
+
cwd: exec?.agent?.session?.header?.cwd,
|
|
48
|
+
...(definition.model ? { agentOptions: { model: definition.model } } : {}),
|
|
49
|
+
},
|
|
50
|
+
signal: controller.signal,
|
|
51
|
+
delivery: "caller",
|
|
52
|
+
});
|
|
53
|
+
if (activation.childId && activation.childId !== reservedId) {
|
|
54
|
+
activeId = String(activation.childId);
|
|
55
|
+
setChildPolicy(activeId, definition);
|
|
56
|
+
clearChildPolicy(reservedId);
|
|
57
|
+
}
|
|
58
|
+
const result = await activation.result;
|
|
59
|
+
const text = textFromOutput(result?.output);
|
|
60
|
+
await activation.dispose?.();
|
|
61
|
+
if (!text) throw new Error(`project agent ${definition.name} returned no text`);
|
|
62
|
+
return { text, stopReason: result?.stopReason ?? "completed" };
|
|
63
|
+
} finally {
|
|
64
|
+
clearChildPolicy(reservedId);
|
|
65
|
+
clearChildPolicy(activeId);
|
|
66
|
+
signal?.removeEventListener?.("abort", abort);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function projectAgentSummary(projectDir, dshDir) {
|
|
71
|
+
return loadAgents(projectDir, dshDir).map((agent) => ({
|
|
72
|
+
name: agent.name,
|
|
73
|
+
description: agent.description,
|
|
74
|
+
model: agent.model,
|
|
75
|
+
}));
|
|
76
|
+
}
|