@goodandready/dsh-goal 0.1.10 → 0.2.0
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/README.md +10 -0
- package/README.ru.md +6 -0
- package/README.zh.md +2 -0
- package/docs/design/DESIGN.md +43 -0
- package/lib/client.js +358 -11
- package/lib/goal-engine.js +988 -780
- package/lib/index.js +371 -317
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,6 +44,12 @@ Complex engineering tasks require multi-step autonomy: decomposing high-level ob
|
|
|
44
44
|
Without an autonomous tracking framework, agents can lose context across turns, stall in passive loops, or fail to notify users when complex workflows stall.
|
|
45
45
|
|
|
46
46
|
**`@goodandready/dsh-goal`** introduces Goal Mode to DeepSeek Harness:
|
|
47
|
+
* 💡 **Live Steering & Nudge (*Added in v0.2.0*)**: Dynamically clarify, amend, or guide the agent's active objective on the fly without aborting the loop.
|
|
48
|
+
* 🔔 **Native Browser Notifications (*Added in v0.2.0*)**: Zero-dependency desktop push notifications via HTML5 `Notification API` when long-running goals complete or require attention.
|
|
49
|
+
* 📑 **10 Quick Launch Presets (*Added in v0.2.0*)**: Ready-to-use engineering prompt chips (Fix Bug, Refactor YAGNI, Tests & Coverage, Code Review, Security Audit, New Feature, Docs, Upgrade Deps, Dead Code Cleanup, Performance).
|
|
50
|
+
* 🛡️ **Tool-Failure Breaker (*Added in v0.2.0*)**: Configurable consecutive tool error limit (`consecutiveToolFailureLimit`, default: 3) to halt loops upon repetitive tool crashes and prevent token burn.
|
|
51
|
+
* 📌 **Git Checkpoint Snapshot (*Added in v0.2.0*)**: Automatically captures start commit hash (`git rev-parse --short HEAD`) with 1-click `git diff` / rollback command in details modal.
|
|
52
|
+
* 🐙 **GitHub & Gitea PR Comment Export (*Added in v0.2.0*)**: One-click formatted comment generator with collapsible `<details><summary>` milestone cards and token telemetry.
|
|
47
53
|
* 🎯 **Sticky Top Goal Banner**: Pinned header over composer dock with live elapsed timer (`• 2s`, `• 1m 45s`), real-time status badge (`RUNNING`, `PAUSED`, `COMPLETED`), active goal title, and control actions.
|
|
48
54
|
* ⏸️ **Play / Pause / Resume / Cancel**: Instantly pause the autonomous loop or resume execution on demand via buttons or `/goal` command.
|
|
49
55
|
* 📋 **Milestone Breakdown & ETA**: Interactive checklist showing sub-tasks, completion status (`pending`, `in_progress`, `completed`, `failed`), progress bar, and dynamic completion ETA.
|
|
@@ -194,6 +200,8 @@ dsh-goal:
|
|
|
194
200
|
autoDrive: true
|
|
195
201
|
enableSound: true
|
|
196
202
|
showQuickLaunchButton: true
|
|
203
|
+
consecutiveToolFailureLimit: 3
|
|
204
|
+
enableBrowserNotifications: true
|
|
197
205
|
```
|
|
198
206
|
|
|
199
207
|
| Parameter | Type | Default | Description |
|
|
@@ -202,6 +210,8 @@ dsh-goal:
|
|
|
202
210
|
| `autoDrive` | `boolean` | `true` | Keep the autonomous agent loop running between turns |
|
|
203
211
|
| `enableSound` | `boolean` | `true` | Play audio chime when a goal completes or fails |
|
|
204
212
|
| `showQuickLaunchButton` | `boolean` | `true` | Show the quick launch goal button above the composer dock |
|
|
213
|
+
| `consecutiveToolFailureLimit` | `number` | `3` | Auto-pause goal if N consecutive turns hit tool execution errors (0 to disable) |
|
|
214
|
+
| `enableBrowserNotifications` | `boolean` | `true` | Show native desktop push notifications on goal completion or failure |
|
|
205
215
|
|
|
206
216
|
---
|
|
207
217
|
|
package/README.ru.md
CHANGED
|
@@ -41,6 +41,12 @@
|
|
|
41
41
|
Решение комплексных инженерных задач требует автономного многошагового выполнения: декомпозиции целей на майлстоуны, непрерывного выполнения итераций без ручного подталкивания пользователя и наглядного отслеживания прогресса.
|
|
42
42
|
|
|
43
43
|
**`@goodandready/dsh-goal`** внедряет автономный режим достижения целей в DeepSeek Harness через команду `/goal`:
|
|
44
|
+
* 💡 **Живая корректировка цели / Направление агента (*v0.2.0*)**: Возможность вносить точечные правки и уточнения в активную цель на лету без прерывания сессии.
|
|
45
|
+
* 🔔 **Браузерные уведомления (*v0.2.0*)**: Нативные пуш-уведомления через HTML5 `Notification API` по завершении или сбое цели.
|
|
46
|
+
* 📑 **10 инженерных пресетов (*v0.2.0*)**: Готовые кликабельные шаблоны задач (Fix Bug, Refactor YAGNI, Tests, Code Review, Security, Docs, Upgrade Deps, Cleanup, Performance) в окне запуска.
|
|
47
|
+
* 🛡️ **Tool-Failure Breaker (*v0.2.0*)**: Настраиваемый порог ошибок инструментов (`consecutiveToolFailureLimit`, по умолчанию 3) для предотвращения бесконечных сбойных вызовов.
|
|
48
|
+
* 📌 **Git Checkpoint (*v0.2.0*)**: Фиксация коммита старта (`git rev-parse --short HEAD`) и копирование команды `git diff` в 1 клик.
|
|
49
|
+
* 🐙 **Экспорт отчета для GitHub / Gitea PR (*v0.2.0*)**: Генерация отчета со сворачиваемыми спойлерами `<details><summary>` для PR и задач.
|
|
44
50
|
* 🎯 **Плавающий баннер в шапке**: зафиксированный статус-бар над чатом с таймером выполнения (`• 2s`, `• 1m 45s`), заголовком цели и кнопками управления.
|
|
45
51
|
* ⏸️ **Управление циклом (Play / Pause / Cancel)**: мгновенная приостановка автономного цикла агента или его возобновление в любой момент.
|
|
46
52
|
* 📋 **Интерактивная панель майлстоунов**: модальное окно со списком подзадач, прогресс-баром и журналом выполнения.
|
package/README.zh.md
CHANGED
|
@@ -193,6 +193,8 @@ dsh-goal:
|
|
|
193
193
|
autoDrive: true
|
|
194
194
|
enableSound: true
|
|
195
195
|
showQuickLaunchButton: true
|
|
196
|
+
| `consecutiveToolFailureLimit` | `number` | `3` | 连续 N 轮工具错误自动熔断暂停(0 为禁用) |
|
|
197
|
+
| `enableBrowserNotifications` | `boolean` | `true` | 目标完成时发送桌面原生系统通知 |
|
|
196
198
|
```
|
|
197
199
|
|
|
198
200
|
| 参数项 | 类型 | 默认值 | 说明 |
|
package/docs/design/DESIGN.md
CHANGED
|
@@ -139,3 +139,46 @@
|
|
|
139
139
|
- Все перехваченные инструменты возвращают структуры, валидные по спецификации `GOAL_VALUE_SCHEMA` ядра (`goal: null` при отсутствии активной цели, либо объект цели с `id`, `revision`, `objective`, `phase`, `roundsStarted`, `maxGoalRounds`, `activation`).
|
|
140
140
|
4. **Нейтрализация устаревшего системного промпта ядра:**
|
|
141
141
|
- Удаляется секция `tool:goal` ядра с ошибочными директивами и заменяется актуальной динамической инструкцией `tool:dsh-goal`.
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
### Решение 11: Живая корректировка цели (Live Steering / Nudge), браузерные уведомления, 10 инженерных пресетов, защита от сбоев инструментов и Git Checkpoints (v0.2.0)
|
|
145
|
+
|
|
146
|
+
**Контекст:**
|
|
147
|
+
В процессе активной эксплуатации автономного режима Goal Mode пользователям требовались:
|
|
148
|
+
1. Возможность вносить точечные корректировки и уточнения в активную или приостановленную цель без прерывания сессии или перезапуска;
|
|
149
|
+
2. Получение нативных уведомлений операционной системы при завершении длительных фоновых задач;
|
|
150
|
+
3. Быстрый выбор типовых инженерных задач (багфикс, рефакторинг, тесты, код-ревью и др.) без ручного набора стандартных промптов;
|
|
151
|
+
4. Защита от бесконечных повторных вызовов падающих инструментов (Tool-Failure Breaker) с настраиваемым порогом;
|
|
152
|
+
5. Фиксация точки старта в репозитории (Git Checkpoint) для моментального просмотра изменений и отката;
|
|
153
|
+
6. Экспорт структурированного отчета с интерактивными спойлерами для комментариев в GitHub / Gitea PR.
|
|
154
|
+
|
|
155
|
+
**Принятые решения:**
|
|
156
|
+
1. **Live Steering / Nudge**:
|
|
157
|
+
- Реализован метод `engine.nudge(text, sid)` и эндпоинт `POST /dsh-goal/action` с `action: 'nudge'`.
|
|
158
|
+
- Поле ввода в модальном окне деталей позволяет оператору направить агента.
|
|
159
|
+
- Указание сохраняется в `goal.nudges` и инжектируется в следующий промпт автодрайва как высокоприоритетная директива `🚨 URGENT USER CLARIFICATION / STEERING`.
|
|
160
|
+
2. **Нативные браузерные уведомления (Notification API)**:
|
|
161
|
+
- При завершении цели (`COMPLETED` или `FAILED`) клиент вызывает системный push через `new Notification(...)` при наличии разрешения.
|
|
162
|
+
- В карточке настроек добавлен чекбокс `enableBrowserNotifications` с интерактивным запросом прав.
|
|
163
|
+
3. **10 инженерных шаблонов запуска (Quick Launch Presets)**:
|
|
164
|
+
- В окно быстрого запуска добавлена лента чипов с 10 сценариями на трёх языках (`en`, `ru`, `zh`):
|
|
165
|
+
1. 🐛 Fix Bug & Verify
|
|
166
|
+
2. ⚡ Refactor (YAGNI)
|
|
167
|
+
3. 📝 Tests & Coverage
|
|
168
|
+
4. 🔍 Code Review
|
|
169
|
+
5. 🚀 New Feature
|
|
170
|
+
6. 🛡️ Security Audit
|
|
171
|
+
7. 📚 Docs & Contract
|
|
172
|
+
8. 📦 Upgrade Deps
|
|
173
|
+
9. 🧹 Dead Code Cleanup
|
|
174
|
+
10. ⚙️ Performance
|
|
175
|
+
4. **Защита от циклических сбоев инструментов (Tool-Failure Breaker)**:
|
|
176
|
+
- В `GoalEngine` внедрен счетчик последовательных ошибок инструментов (`consecutiveToolFailureLimit`, по умолчанию 3, 0 — отключено).
|
|
177
|
+
- При превышении порога цель автоматически переводится в `PAUSED` с фиксацией точной причины в журнале.
|
|
178
|
+
- Порог настраивается через Schemastery карточку в интерфейсе настроек.
|
|
179
|
+
5. **Git Checkpoint Snapshot**:
|
|
180
|
+
- При старте цели через `node:child_process` фиксируется короткий хеш `git rev-parse --short HEAD` (с таймаутом 1 сек).
|
|
181
|
+
- В окне деталей отображается бейдж `📌 Git: <commit>` с копированием команды `git diff <commit>` в один клик.
|
|
182
|
+
6. **Экспорт отчета для GitHub / Gitea PR**:
|
|
183
|
+
- Метод `exportReportGitHubPR(state)` форматирует статус, время, расход токенов, резюме и таблицу вех внутри сворачиваемого блока `<details><summary>`.
|
|
184
|
+
- В окне деталей добавлена кнопка «🐙 Копировать для GitHub PR».
|
package/lib/client.js
CHANGED
|
@@ -15,6 +15,21 @@ window.__ModuleLoader__.load({
|
|
|
15
15
|
Primitives = null;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
|
|
19
|
+
// --- 10 ИНЖЕНЕРНЫХ ШАБЛОНОВ БЫСТРОГО ЗАПУСКА ЦЕЛИ ---
|
|
20
|
+
const QUICK_LAUNCH_TEMPLATES = [
|
|
21
|
+
{ id: 'fix', icon: '🐛', label: { en: 'Fix Bug', ru: 'Исправить баг', zh: '修复缺陷' }, prefix: { en: 'Investigate bug, pinpoint root cause, apply fix, and verify with tests: ', ru: 'Исследуй ошибку, локализуй причину, устрани дефект и подтверди тестами: ', zh: '调查并定位缺陷根源,实施修复并通过测试验证:' } },
|
|
22
|
+
{ id: 'refactor', icon: '⚡', label: { en: 'Refactor (YAGNI)', ru: 'Рефакторинг', zh: '重构精简' }, prefix: { en: 'Refactor module applying YAGNI principle, delete redundant abstractions, and verify: ', ru: 'Проведи рефакторинг модуля по принципу YAGNI, удали избыточные абстракции и проверь: ', zh: '遵循 YAGNI 原则重构模块,删除冗余抽象并验证:' } },
|
|
23
|
+
{ id: 'tests', icon: '📝', label: { en: 'Tests & Coverage', ru: 'Написать тесты', zh: '补充测试' }, prefix: { en: 'Write comprehensive unit tests covering edge cases for: ', ru: 'Напиши исчерпывающий набор модульных тестов с проверкой граничных случаев для: ', zh: '为以下模块编写覆盖边界情况的完整单元测试:' } },
|
|
24
|
+
{ id: 'review', icon: '🔍', label: { en: 'Code Review', ru: 'Код-ревью', zh: '代码审计' }, prefix: { en: 'Review codebase for subtle bugs, memory leaks, and over-engineering in: ', ru: 'Проанализируй кодовую базу на предмет скрытых багов, утечек памяти и оверинжиниринга в: ', zh: '对代码库进行深入审查,排查潜在缺陷与过度设计:' } },
|
|
25
|
+
{ id: 'feature', icon: '🚀', label: { en: 'New Feature', ru: 'Новая фича', zh: '新功能开发' }, prefix: { en: 'Implement new feature according to specification and add tests for: ', ru: 'Реализуй новую функциональность по спецификации и добавь тесты для: ', zh: '根据规格说明实现新功能并补充测试:' } },
|
|
26
|
+
{ id: 'security', icon: '🛡️', label: { en: 'Security Audit', ru: 'Безопасность', zh: '安全加固' }, prefix: { en: 'Audit security boundaries, sanitize inputs, and prevent injection in: ', ru: 'Проведи аудит безопасности, добавь санитизацию ввода и защиту от инъекций в: ', zh: '审计安全边界,增加输入过滤与防注入保护:' } },
|
|
27
|
+
{ id: 'docs', icon: '📚', label: { en: 'Docs & Contract', ru: 'Документация', zh: '文档规范' }, prefix: { en: 'Update technical documentation, README, and DESIGN.md conforming to standard for: ', ru: 'Актуализируй техническую документацию, README и DESIGN.md по стандарту для: ', zh: '按标准更新技术文档、README 与 DESIGN.md:' } },
|
|
28
|
+
{ id: 'upgrade', icon: '📦', label: { en: 'Upgrade Deps', ru: 'Обновить зависимости', zh: '依赖升级' }, prefix: { en: 'Upgrade dependencies, resolve breaking API changes, and verify build for: ', ru: 'Обнови зависимости, разреши несовместимости версий и проверь сборку для: ', zh: '安全升级依赖库,解决接口兼容性并确保构建通过:' } },
|
|
29
|
+
{ id: 'cleanup', icon: '🧹', label: { en: 'Dead Code', ru: 'Чистка кода', zh: '清理死代码' }, prefix: { en: 'Find and safely remove dead code, unused exports, and stale fixtures in: ', ru: 'Найди и удали мёртвый код, неиспользуемые экспорты и устаревшие файлы в: ', zh: '查找并安全移除死代码、未使用的导出与失效测试用例:' } },
|
|
30
|
+
{ id: 'perf', icon: '⚙️', label: { en: 'Performance', ru: 'Оптимизация', zh: '性能优化' }, prefix: { en: 'Profile performance bottlenecks, eliminate unnecessary blocking calls in: ', ru: 'Профилируй узкие места, устрани лишние блокировки и оптимизируй отклик в: ', zh: '分析性能瓶颈,消除阻塞调用并优化响应速度:' } },
|
|
31
|
+
];
|
|
32
|
+
|
|
18
33
|
// --- SVG ИКОНКИ ---
|
|
19
34
|
|
|
20
35
|
function IconTarget({ size = 15, className = '' }) {
|
|
@@ -186,6 +201,56 @@ window.__ModuleLoader__.load({
|
|
|
186
201
|
justify-content: flex-start;
|
|
187
202
|
margin-bottom: 4px;
|
|
188
203
|
}
|
|
204
|
+
|
|
205
|
+
.dsh-goal-template-chips {
|
|
206
|
+
display: flex;
|
|
207
|
+
flex-wrap: wrap;
|
|
208
|
+
gap: 6px;
|
|
209
|
+
margin-bottom: 12px;
|
|
210
|
+
}
|
|
211
|
+
.dsh-goal-template-chip {
|
|
212
|
+
appearance: none;
|
|
213
|
+
font: inherit;
|
|
214
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
215
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
216
|
+
border-radius: 999px;
|
|
217
|
+
padding: 3px 8px;
|
|
218
|
+
font-size: 11px;
|
|
219
|
+
color: var(--dsw-alias-label-secondary);
|
|
220
|
+
cursor: pointer;
|
|
221
|
+
display: inline-flex;
|
|
222
|
+
align-items: center;
|
|
223
|
+
gap: 4px;
|
|
224
|
+
transition: all 0.12s ease;
|
|
225
|
+
}
|
|
226
|
+
.dsh-goal-template-chip:hover {
|
|
227
|
+
background: var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-3));
|
|
228
|
+
border-color: var(--dsw-alias-label-tertiary);
|
|
229
|
+
color: var(--dsw-alias-label-primary);
|
|
230
|
+
}
|
|
231
|
+
.dsh-goal-nudge-box {
|
|
232
|
+
display: flex;
|
|
233
|
+
align-items: center;
|
|
234
|
+
gap: 8px;
|
|
235
|
+
margin-top: 4px;
|
|
236
|
+
}
|
|
237
|
+
.dsh-goal-git-badge {
|
|
238
|
+
display: inline-flex;
|
|
239
|
+
align-items: center;
|
|
240
|
+
gap: 4px;
|
|
241
|
+
font-family: monospace;
|
|
242
|
+
font-size: 11px;
|
|
243
|
+
padding: 2px 6px;
|
|
244
|
+
border-radius: 4px;
|
|
245
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
246
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
247
|
+
color: var(--dsw-alias-label-secondary);
|
|
248
|
+
cursor: pointer;
|
|
249
|
+
}
|
|
250
|
+
.dsh-goal-git-badge:hover {
|
|
251
|
+
color: var(--dsw-alias-label-primary);
|
|
252
|
+
border-color: var(--dsw-alias-label-tertiary);
|
|
253
|
+
}
|
|
189
254
|
.dsh-goal-quicklaunch-btn {
|
|
190
255
|
appearance: none;
|
|
191
256
|
font: inherit;
|
|
@@ -699,6 +764,51 @@ window.__ModuleLoader__.load({
|
|
|
699
764
|
} catch (_) {}
|
|
700
765
|
}
|
|
701
766
|
|
|
767
|
+
|
|
768
|
+
// --- ГЕНЕРАТОР ОТЧЕТА ДЛЯ GITHUB / GITEA PR COMMENT ---
|
|
769
|
+
function generateGitHubPRComment(state) {
|
|
770
|
+
if (!state) return '';
|
|
771
|
+
const title = state.title || 'Goal Report';
|
|
772
|
+
const status = state.state === 'COMPLETED' ? 'COMPLETED' : state.state;
|
|
773
|
+
const elapsed = state.formattedElapsed || '0s';
|
|
774
|
+
const iter = `${state.iterationsCount || 0}/${state.maxIterations || 25}`;
|
|
775
|
+
const totalTokens = state.tokensUsage?.totalTokens || 0;
|
|
776
|
+
const promptTokens = state.tokensUsage?.promptTokens || 0;
|
|
777
|
+
const compTokens = state.tokensUsage?.completionTokens || 0;
|
|
778
|
+
const gitCommit = state.gitStartCommit ? ` | **Git Start:** \`${state.gitStartCommit}\` 📌` : '';
|
|
779
|
+
|
|
780
|
+
let md = `## 🎯 Autonomous Goal Resolution: ${title}\n\n`;
|
|
781
|
+
md += `> **Status:** \`${status}\` 🚀 | **Duration:** \`${elapsed}\` ⏱️ | **Iterations:** \`${iter}\` 🔄${gitCommit}\n\n`;
|
|
782
|
+
|
|
783
|
+
if (totalTokens > 0) {
|
|
784
|
+
md += `### 📊 Telemetry & Token Usage\n`;
|
|
785
|
+
md += `- **Total Tokens:** \`${totalTokens.toLocaleString('en-US')}\` (Prompt: \`${promptTokens.toLocaleString('en-US')}\`, Completion: \`${compTokens.toLocaleString('en-US')}\`)\n\n`;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (state.resultSummary) {
|
|
789
|
+
md += `### 📦 Deliverables & Achievements\n${state.resultSummary}\n\n`;
|
|
790
|
+
} else if (state.description) {
|
|
791
|
+
md += `### 📝 Objective\n${state.description}\n\n`;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const milestones = state.milestones || [];
|
|
795
|
+
if (milestones.length > 0) {
|
|
796
|
+
const completedCount = milestones.filter(m => m.status === 'completed').length;
|
|
797
|
+
md += `<details>\n<summary><b>📋 Milestones Breakdown (${completedCount}/${milestones.length} Completed)</b></summary>\n\n`;
|
|
798
|
+
md += `| # | Status | Milestone | Notes |\n|---|---|---|---|\n`;
|
|
799
|
+
milestones.forEach((m, idx) => {
|
|
800
|
+
const mark = m.status === 'completed' ? '✅ Done' : m.status === 'in_progress' ? '🔄 In Progress' : '⏳ Pending';
|
|
801
|
+
const cleanTitle = (m.title || '').replace(/\|/g, '\\|');
|
|
802
|
+
const cleanNotes = (m.notes || '').replace(/\|/g, '\\|');
|
|
803
|
+
md += `| ${idx + 1} | ${mark} | ${cleanTitle} | ${cleanNotes || '—'} |\n`;
|
|
804
|
+
});
|
|
805
|
+
md += `\n</details>\n\n`;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
md += `*Automated by [@goodandready/dsh-goal](https://github.com/GooDAnDReaDY/dsh-goal)*\n`;
|
|
809
|
+
return md;
|
|
810
|
+
}
|
|
811
|
+
|
|
702
812
|
// --- ГЕНЕРАТОР MARKDOWN-ОТЧЕТА ---
|
|
703
813
|
function generateMarkdownReport(state) {
|
|
704
814
|
if (!state) return '';
|
|
@@ -713,7 +823,7 @@ window.__ModuleLoader__.load({
|
|
|
713
823
|
let md = `# 🎯 Goal Report: ${title}\n\n`;
|
|
714
824
|
md += `**Status:** \`${status}\` | **Duration:** \`${elapsed}\` | **Iterations:** \`${iter}\`\n`;
|
|
715
825
|
if (totalTokens > 0) {
|
|
716
|
-
md += `**Tokens:** \`${totalTokens.toLocaleString()}\` (Prompt: \`${promptTokens.toLocaleString()}\`, Completion: \`${compTokens.toLocaleString()}\`)\n`;
|
|
826
|
+
md += `**Tokens:** \`${totalTokens.toLocaleString('en-US')}\` (Prompt: \`${promptTokens.toLocaleString('en-US')}\`, Completion: \`${compTokens.toLocaleString('en-US')}\`)\n`;
|
|
717
827
|
}
|
|
718
828
|
md += '\n';
|
|
719
829
|
|
|
@@ -748,6 +858,10 @@ window.__ModuleLoader__.load({
|
|
|
748
858
|
const milestones = state.milestones || [];
|
|
749
859
|
const isCompleted = state.state === 'COMPLETED';
|
|
750
860
|
const [copied, setCopied] = useState(false);
|
|
861
|
+
const [prCopied, setPrCopied] = useState(false);
|
|
862
|
+
const [gitCopied, setGitCopied] = useState(false);
|
|
863
|
+
const [nudgeText, setNudgeText] = useState('');
|
|
864
|
+
const [nudgeSent, setNudgeSent] = useState(false);
|
|
751
865
|
|
|
752
866
|
const totalTokens = state.tokensUsage?.totalTokens || 0;
|
|
753
867
|
const promptTokens = state.tokensUsage?.promptTokens || 0;
|
|
@@ -756,7 +870,7 @@ window.__ModuleLoader__.load({
|
|
|
756
870
|
? (totalTokens >= 1000 ? `${(totalTokens / 1000).toFixed(1)}k` : `${totalTokens}`)
|
|
757
871
|
: '—';
|
|
758
872
|
const tokensTooltip = totalTokens > 0
|
|
759
|
-
? `Prompt: ${promptTokens.toLocaleString()} | Completion: ${compTokens.toLocaleString()} | Total: ${totalTokens.toLocaleString()}`
|
|
873
|
+
? `Prompt: ${promptTokens.toLocaleString('en-US')} | Completion: ${compTokens.toLocaleString('en-US')} | Total: ${totalTokens.toLocaleString('en-US')}`
|
|
760
874
|
: 'Токены ещё не зафиксированы';
|
|
761
875
|
|
|
762
876
|
const handleCopyReport = () => {
|
|
@@ -810,6 +924,67 @@ window.__ModuleLoader__.load({
|
|
|
810
924
|
React.createElement('div', { style: { fontWeight: 600, fontSize: 16, marginBottom: 4, color: 'var(--dsw-alias-label-primary)' } }, state.title),
|
|
811
925
|
state.description ? React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', fontSize: 13, marginBottom: 6, lineHeight: 1.4 } }, state.description) : null,
|
|
812
926
|
),
|
|
927
|
+
!isCompleted
|
|
928
|
+
? React.createElement(
|
|
929
|
+
'div',
|
|
930
|
+
{ className: 'dsh-goal-nudge-box', style: { marginBottom: 12 } },
|
|
931
|
+
React.createElement('input', {
|
|
932
|
+
type: 'text',
|
|
933
|
+
className: 'dsh-goal-card-input',
|
|
934
|
+
style: { flex: 1, fontSize: 12 },
|
|
935
|
+
placeholder: t('nudgePlaceholder') || '💡 Уточнить задачу или направить агента...',
|
|
936
|
+
value: nudgeText,
|
|
937
|
+
onChange: (e) => setNudgeText(e.target.value),
|
|
938
|
+
onKeyDown: (e) => {
|
|
939
|
+
if (e.key === 'Enter' && nudgeText.trim()) {
|
|
940
|
+
onAction('nudge', { text: nudgeText.trim() });
|
|
941
|
+
setNudgeText('');
|
|
942
|
+
setNudgeSent(true);
|
|
943
|
+
setTimeout(() => setNudgeSent(false), 2000);
|
|
944
|
+
}
|
|
945
|
+
},
|
|
946
|
+
}),
|
|
947
|
+
React.createElement(
|
|
948
|
+
'button',
|
|
949
|
+
{
|
|
950
|
+
type: 'button',
|
|
951
|
+
className: `dsh-goal-btn ${nudgeSent ? 'dsh-goal-btn-primary' : ''}`,
|
|
952
|
+
disabled: !nudgeText.trim(),
|
|
953
|
+
onClick: () => {
|
|
954
|
+
if (nudgeText.trim()) {
|
|
955
|
+
onAction('nudge', { text: nudgeText.trim() });
|
|
956
|
+
setNudgeText('');
|
|
957
|
+
setNudgeSent(true);
|
|
958
|
+
setTimeout(() => setNudgeSent(false), 2000);
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
},
|
|
962
|
+
nudgeSent ? (t('nudgeSent') || '✅ Отправлено!') : (t('sendNudge') || 'Направить'),
|
|
963
|
+
),
|
|
964
|
+
)
|
|
965
|
+
: null,
|
|
966
|
+
state.gitStartCommit
|
|
967
|
+
? React.createElement(
|
|
968
|
+
'div',
|
|
969
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 8 } },
|
|
970
|
+
React.createElement(
|
|
971
|
+
'span',
|
|
972
|
+
{
|
|
973
|
+
className: 'dsh-goal-git-badge',
|
|
974
|
+
title: t('copyGitDiff') || 'Кликните, чтобы скопировать команду git diff',
|
|
975
|
+
onClick: () => {
|
|
976
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
|
|
977
|
+
navigator.clipboard.writeText(`git diff ${state.gitStartCommit}`).then(() => {
|
|
978
|
+
setGitCopied(true);
|
|
979
|
+
setTimeout(() => setGitCopied(false), 2000);
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
},
|
|
983
|
+
},
|
|
984
|
+
gitCopied ? '✅ diff скопирован!' : `📌 Git: ${state.gitStartCommit}`,
|
|
985
|
+
),
|
|
986
|
+
)
|
|
987
|
+
: null,
|
|
813
988
|
React.createElement(
|
|
814
989
|
'div',
|
|
815
990
|
{ className: 'dsh-goal-stat-grid' },
|
|
@@ -880,13 +1055,34 @@ window.__ModuleLoader__.load({
|
|
|
880
1055
|
{ className: 'dsh-goal-modal-foot' },
|
|
881
1056
|
isCompleted
|
|
882
1057
|
? React.createElement(
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1058
|
+
React.Fragment,
|
|
1059
|
+
null,
|
|
1060
|
+
React.createElement(
|
|
1061
|
+
'button',
|
|
1062
|
+
{
|
|
1063
|
+
className: `dsh-goal-btn dsh-goal-btn-copy${copied ? ' copied' : ''}`,
|
|
1064
|
+
onClick: handleCopyReport,
|
|
1065
|
+
title: t('copyReport') || 'Скопировать отчёт в Markdown',
|
|
1066
|
+
},
|
|
1067
|
+
copied ? (t('reportCopied') || '✅ Скопировано!') : (t('copyReport') || '📋 Скопировать отчёт в Markdown'),
|
|
1068
|
+
),
|
|
1069
|
+
React.createElement(
|
|
1070
|
+
'button',
|
|
1071
|
+
{
|
|
1072
|
+
className: `dsh-goal-btn dsh-goal-btn-copy${prCopied ? ' copied' : ''}`,
|
|
1073
|
+
onClick: () => {
|
|
1074
|
+
const prMd = generateGitHubPRComment(state);
|
|
1075
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
|
|
1076
|
+
navigator.clipboard.writeText(prMd).then(() => {
|
|
1077
|
+
setPrCopied(true);
|
|
1078
|
+
setTimeout(() => setPrCopied(false), 2500);
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
},
|
|
1082
|
+
title: 'Скопировать отчёт для GitHub / Gitea PR Comment',
|
|
1083
|
+
},
|
|
1084
|
+
prCopied ? (t('prCommentCopied') || '✅ PR-отчёт скопирован!') : (t('copyPRComment') || '🐙 Копировать для GitHub PR'),
|
|
1085
|
+
),
|
|
890
1086
|
)
|
|
891
1087
|
: null,
|
|
892
1088
|
isCompleted
|
|
@@ -968,6 +1164,29 @@ window.__ModuleLoader__.load({
|
|
|
968
1164
|
{ style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)', marginBottom: 8 } },
|
|
969
1165
|
t('quickLaunchDesc') || 'Сформулируйте задачу для агента в автономном режиме:',
|
|
970
1166
|
),
|
|
1167
|
+
React.createElement(
|
|
1168
|
+
'div',
|
|
1169
|
+
{ className: 'dsh-goal-template-chips' },
|
|
1170
|
+
QUICK_LAUNCH_TEMPLATES.map((tmpl) => {
|
|
1171
|
+
const lang = t('localeCode') || 'ru';
|
|
1172
|
+
const label = tmpl.label[lang] || tmpl.label.en;
|
|
1173
|
+
const prefix = tmpl.prefix[lang] || tmpl.prefix.en;
|
|
1174
|
+
return React.createElement(
|
|
1175
|
+
'button',
|
|
1176
|
+
{
|
|
1177
|
+
key: tmpl.id,
|
|
1178
|
+
type: 'button',
|
|
1179
|
+
className: 'dsh-goal-template-chip',
|
|
1180
|
+
title: prefix,
|
|
1181
|
+
onClick: () => {
|
|
1182
|
+
setTitle(prefix);
|
|
1183
|
+
if (inputRef.current) inputRef.current.focus();
|
|
1184
|
+
},
|
|
1185
|
+
},
|
|
1186
|
+
`${tmpl.icon} ${label}`,
|
|
1187
|
+
);
|
|
1188
|
+
}),
|
|
1189
|
+
),
|
|
971
1190
|
React.createElement('input', {
|
|
972
1191
|
ref: inputRef,
|
|
973
1192
|
type: 'text',
|
|
@@ -1101,6 +1320,22 @@ window.__ModuleLoader__.load({
|
|
|
1101
1320
|
} else if (nextState.state === 'FAILED') {
|
|
1102
1321
|
playGoalChime(false);
|
|
1103
1322
|
}
|
|
1323
|
+
|
|
1324
|
+
// HTML5 Browser Desktop Notification
|
|
1325
|
+
if (typeof window !== 'undefined' && 'Notification' in window) {
|
|
1326
|
+
if (nextState.enableBrowserNotifications !== false && Notification.permission === 'granted') {
|
|
1327
|
+
try {
|
|
1328
|
+
const isOk = nextState.state === 'COMPLETED';
|
|
1329
|
+
new Notification(
|
|
1330
|
+
isOk ? '🎯 Цель выполнена' : '⚠️ Ошибка цели',
|
|
1331
|
+
{
|
|
1332
|
+
body: `${nextState.title} (${nextState.formattedElapsed || ''})`,
|
|
1333
|
+
icon: '/favicon.ico',
|
|
1334
|
+
}
|
|
1335
|
+
);
|
|
1336
|
+
} catch (_) {}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1104
1339
|
}
|
|
1105
1340
|
}
|
|
1106
1341
|
lastStateRef.current = nextState;
|
|
@@ -1393,6 +1628,8 @@ window.__ModuleLoader__.load({
|
|
|
1393
1628
|
autoDrive: true,
|
|
1394
1629
|
enableSound: true,
|
|
1395
1630
|
showQuickLaunchButton: true,
|
|
1631
|
+
consecutiveToolFailureLimit: 3,
|
|
1632
|
+
enableBrowserNotifications: true,
|
|
1396
1633
|
};
|
|
1397
1634
|
|
|
1398
1635
|
function parseNumberField(raw) {
|
|
@@ -1524,9 +1761,11 @@ window.__ModuleLoader__.load({
|
|
|
1524
1761
|
const autoDriveStatus = getFieldStatus('autoDrive', draft?.autoDrive, snap);
|
|
1525
1762
|
const enableSoundStatus = getFieldStatus('enableSound', draft?.enableSound, snap);
|
|
1526
1763
|
const quickLaunchStatus = getFieldStatus('showQuickLaunchButton', draft?.showQuickLaunchButton, snap);
|
|
1764
|
+
const toolFailStatus = getFieldStatus('consecutiveToolFailureLimit', draft?.consecutiveToolFailureLimit, snap);
|
|
1765
|
+
const notifStatus = getFieldStatus('enableBrowserNotifications', draft?.enableBrowserNotifications, snap);
|
|
1527
1766
|
|
|
1528
|
-
const dirty = maxIterStatus.isDirty || autoDriveStatus.isDirty || enableSoundStatus.isDirty || quickLaunchStatus.isDirty;
|
|
1529
|
-
const invalid = maxIterStatus.invalid;
|
|
1767
|
+
const dirty = maxIterStatus.isDirty || autoDriveStatus.isDirty || enableSoundStatus.isDirty || quickLaunchStatus.isDirty || toolFailStatus.isDirty || notifStatus.isDirty;
|
|
1768
|
+
const invalid = maxIterStatus.invalid || toolFailStatus.invalid;
|
|
1530
1769
|
const disabled = !scope || saving || snap?.writable === false;
|
|
1531
1770
|
|
|
1532
1771
|
const edit = (field, val) => {
|
|
@@ -1536,6 +1775,8 @@ window.__ModuleLoader__.load({
|
|
|
1536
1775
|
autoDrive: prev?.autoDrive !== undefined ? prev.autoDrive : autoDriveStatus.value,
|
|
1537
1776
|
enableSound: prev?.enableSound !== undefined ? prev.enableSound : enableSoundStatus.value,
|
|
1538
1777
|
showQuickLaunchButton: prev?.showQuickLaunchButton !== undefined ? prev.showQuickLaunchButton : quickLaunchStatus.value,
|
|
1778
|
+
consecutiveToolFailureLimit: prev?.consecutiveToolFailureLimit !== undefined ? prev.consecutiveToolFailureLimit : toolFailStatus.value,
|
|
1779
|
+
enableBrowserNotifications: prev?.enableBrowserNotifications !== undefined ? prev.enableBrowserNotifications : notifStatus.value,
|
|
1539
1780
|
[field]: val,
|
|
1540
1781
|
}));
|
|
1541
1782
|
};
|
|
@@ -1736,6 +1977,82 @@ window.__ModuleLoader__.load({
|
|
|
1736
1977
|
)
|
|
1737
1978
|
: null,
|
|
1738
1979
|
),
|
|
1980
|
+
React.createElement(
|
|
1981
|
+
'div',
|
|
1982
|
+
{ className: 'dsh-goal-card-field' },
|
|
1983
|
+
React.createElement(
|
|
1984
|
+
'div',
|
|
1985
|
+
{ className: 'dsh-goal-field-label-row' },
|
|
1986
|
+
React.createElement('label', { htmlFor: 'dsh-goal-tool-fail' }, t('toolFailLabel') || 'Лимит ошибок инструментов (Tool-Failure Breaker):'),
|
|
1987
|
+
React.createElement(
|
|
1988
|
+
'div',
|
|
1989
|
+
{ className: 'dsh-goal-field-meta' },
|
|
1990
|
+
toolFailStatus.isOverridden
|
|
1991
|
+
? React.createElement('span', { className: 'dsh-goal-override-tag' }, t('overridden') || 'изменено')
|
|
1992
|
+
: null,
|
|
1993
|
+
toolFailStatus.isOverridden
|
|
1994
|
+
? React.createElement(
|
|
1995
|
+
'button',
|
|
1996
|
+
{
|
|
1997
|
+
type: 'button',
|
|
1998
|
+
className: 'dsh-goal-btn-inline-reset',
|
|
1999
|
+
title: t('resetField') || 'Сбросить к значению по умолчанию',
|
|
2000
|
+
onClick: () => edit('consecutiveToolFailureLimit', snap?.base?.consecutiveToolFailureLimit ?? DEFAULT_SETTINGS.consecutiveToolFailureLimit),
|
|
2001
|
+
},
|
|
2002
|
+
React.createElement(IconRotateCcw, { size: 12 }),
|
|
2003
|
+
t('resetField') || 'По умолчанию',
|
|
2004
|
+
)
|
|
2005
|
+
: null,
|
|
2006
|
+
),
|
|
2007
|
+
),
|
|
2008
|
+
React.createElement('input', {
|
|
2009
|
+
id: 'dsh-goal-tool-fail',
|
|
2010
|
+
type: 'number',
|
|
2011
|
+
min: 0,
|
|
2012
|
+
placeholder: String(toolFailStatus.baseValue),
|
|
2013
|
+
value: toolFailStatus.value !== undefined ? String(toolFailStatus.value) : '',
|
|
2014
|
+
disabled,
|
|
2015
|
+
className: 'dsh-goal-card-input',
|
|
2016
|
+
onChange: (e) => edit('consecutiveToolFailureLimit', parseNumberField(e.target.value)),
|
|
2017
|
+
}),
|
|
2018
|
+
React.createElement(
|
|
2019
|
+
'p',
|
|
2020
|
+
{ className: 'dsh-goal-field-hint' },
|
|
2021
|
+
t('toolFailHint') || 'Приостанавливать цель при N ошибках инструментов подряд (0 — отключить)',
|
|
2022
|
+
),
|
|
2023
|
+
),
|
|
2024
|
+
React.createElement(
|
|
2025
|
+
'div',
|
|
2026
|
+
{ className: 'dsh-goal-check-row' },
|
|
2027
|
+
React.createElement(
|
|
2028
|
+
'label',
|
|
2029
|
+
{ className: 'dsh-goal-check' },
|
|
2030
|
+
React.createElement('input', {
|
|
2031
|
+
type: 'checkbox',
|
|
2032
|
+
checked: notifStatus.value !== false,
|
|
2033
|
+
disabled,
|
|
2034
|
+
onChange: (e) => {
|
|
2035
|
+
edit('enableBrowserNotifications', e.target.checked);
|
|
2036
|
+
if (e.target.checked && typeof window !== 'undefined' && 'Notification' in window && Notification.permission === 'default') {
|
|
2037
|
+
Notification.requestPermission();
|
|
2038
|
+
}
|
|
2039
|
+
},
|
|
2040
|
+
}),
|
|
2041
|
+
t('notifLabel') || 'Браузерные уведомления при завершении цели',
|
|
2042
|
+
),
|
|
2043
|
+
notifStatus.isOverridden
|
|
2044
|
+
? React.createElement(
|
|
2045
|
+
'button',
|
|
2046
|
+
{
|
|
2047
|
+
type: 'button',
|
|
2048
|
+
className: 'dsh-goal-btn-inline-reset',
|
|
2049
|
+
title: t('resetField') || 'Сбросить к значению по умолчанию',
|
|
2050
|
+
onClick: () => resetFieldToDefault('enableBrowserNotifications'),
|
|
2051
|
+
},
|
|
2052
|
+
React.createElement(IconRotateCcw, { size: 12 }),
|
|
2053
|
+
)
|
|
2054
|
+
: null,
|
|
2055
|
+
),
|
|
1739
2056
|
React.createElement(
|
|
1740
2057
|
'div',
|
|
1741
2058
|
{ className: 'dsh-goal-foot' },
|
|
@@ -1809,6 +2126,16 @@ window.__ModuleLoader__.load({
|
|
|
1809
2126
|
saveFailed: 'Not saved — fix the values and try again',
|
|
1810
2127
|
loading: 'Loading settings…',
|
|
1811
2128
|
unavailable: 'Settings unavailable',
|
|
2129
|
+
localeCode: 'en',
|
|
2130
|
+
copyPRComment: '🐙 Copy for GitHub PR',
|
|
2131
|
+
prCommentCopied: '✅ PR Report Copied!',
|
|
2132
|
+
nudgePlaceholder: '💡 Clarify task or steer agent...',
|
|
2133
|
+
sendNudge: 'Steer',
|
|
2134
|
+
nudgeSent: '✅ Steering sent!',
|
|
2135
|
+
copyGitDiff: 'Click to copy git diff command',
|
|
2136
|
+
toolFailLabel: 'Tool-Failure Breaker Limit:',
|
|
2137
|
+
toolFailHint: 'Auto-pause goal if N consecutive turns hit tool execution errors (0 to disable)',
|
|
2138
|
+
notifLabel: 'Native browser desktop notifications on completion',
|
|
1812
2139
|
},
|
|
1813
2140
|
zh: {
|
|
1814
2141
|
goalLabel: '当前目标',
|
|
@@ -1851,6 +2178,16 @@ window.__ModuleLoader__.load({
|
|
|
1851
2178
|
saveFailed: '保存失败 — 请检查并重试',
|
|
1852
2179
|
loading: '正在加载设置…',
|
|
1853
2180
|
unavailable: '设置不可用',
|
|
2181
|
+
localeCode: 'zh',
|
|
2182
|
+
copyPRComment: '🐙 复制为 GitHub PR 报告',
|
|
2183
|
+
prCommentCopied: '✅ PR 报告已复制!',
|
|
2184
|
+
nudgePlaceholder: '💡 补充要求或调整智能体方向...',
|
|
2185
|
+
sendNudge: '下达指令',
|
|
2186
|
+
nudgeSent: '✅ 指令已发送!',
|
|
2187
|
+
copyGitDiff: '点击复制 git diff 命令',
|
|
2188
|
+
toolFailLabel: '工具调用连续故障断路器阈值:',
|
|
2189
|
+
toolFailHint: '连续 N 轮遭遇工具调用错误时自动暂停(0 表示禁用)',
|
|
2190
|
+
notifLabel: '目标完成时发送浏览器桌面原生通知',
|
|
1854
2191
|
},
|
|
1855
2192
|
ru: {
|
|
1856
2193
|
goalLabel: 'Текущая цель',
|
|
@@ -1893,6 +2230,16 @@ window.__ModuleLoader__.load({
|
|
|
1893
2230
|
saveFailed: 'Не сохранено — исправьте и повторите',
|
|
1894
2231
|
loading: 'Загрузка настроек…',
|
|
1895
2232
|
unavailable: 'Настройки недоступны',
|
|
2233
|
+
localeCode: 'ru',
|
|
2234
|
+
copyPRComment: '🐙 Копировать для GitHub PR',
|
|
2235
|
+
prCommentCopied: '✅ PR-отчёт скопирован!',
|
|
2236
|
+
nudgePlaceholder: '💡 Уточнить задачу или направить агента...',
|
|
2237
|
+
sendNudge: 'Направить',
|
|
2238
|
+
nudgeSent: '✅ Подсказка отправлена!',
|
|
2239
|
+
copyGitDiff: 'Кликните, чтобы скопировать команду git diff',
|
|
2240
|
+
toolFailLabel: 'Лимит ошибок инструментов (Tool-Failure Breaker):',
|
|
2241
|
+
toolFailHint: 'Приостанавливать цель при N ошибках инструментов подряд (0 — отключить)',
|
|
2242
|
+
notifLabel: 'Браузерные уведомления на рабочем столе при завершении',
|
|
1896
2243
|
},
|
|
1897
2244
|
};
|
|
1898
2245
|
|