@goodandready/dsh-goal 0.1.10 → 0.2.1

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/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', zh: '修复缺陷' }, prefix: { en: 'Investigate bug, pinpoint root cause, apply fix, and verify with tests: ', zh: '调查并定位缺陷根源,实施修复并通过测试验证:' } },
22
+ { id: 'refactor', icon: '⚡', label: { en: 'Refactor (YAGNI)', zh: '重构精简' }, prefix: { en: 'Refactor module applying YAGNI principle, delete redundant abstractions, and verify: ', zh: '遵循 YAGNI 原则重构模块,删除冗余抽象并验证:' } },
23
+ { id: 'tests', icon: '📝', label: { en: 'Tests & Coverage', zh: '补充测试' }, prefix: { en: 'Write comprehensive unit tests covering edge cases for: ', zh: '为以下模块编写覆盖边界情况的完整单元测试:' } },
24
+ { id: 'review', icon: '🔍', label: { en: 'Code Review', zh: '代码审计' }, prefix: { en: 'Review codebase for subtle bugs, memory leaks, and over-engineering in: ', zh: '对代码库进行深入审查,排查潜在缺陷与过度设计:' } },
25
+ { id: 'feature', icon: '🚀', label: { en: 'New Feature', zh: '新功能开发' }, prefix: { en: 'Implement new feature according to specification and add tests for: ', zh: '根据规格说明实现新功能并补充测试:' } },
26
+ { id: 'security', icon: '🛡️', label: { en: 'Security Audit', zh: '安全加固' }, prefix: { en: 'Audit security boundaries, sanitize inputs, and prevent injection in: ', zh: '审计安全边界,增加输入过滤与防注入保护:' } },
27
+ { id: 'docs', icon: '📚', label: { en: 'Docs & Contract', zh: '文档规范' }, prefix: { en: 'Update technical documentation, README, and DESIGN.md conforming to standard for: ', zh: '按标准更新技术文档、README 与 DESIGN.md:' } },
28
+ { id: 'upgrade', icon: '📦', label: { en: 'Upgrade Deps', zh: '依赖升级' }, prefix: { en: 'Upgrade dependencies, resolve breaking API changes, and verify build for: ', zh: '安全升级依赖库,解决接口兼容性并确保构建通过:' } },
29
+ { id: 'cleanup', icon: '🧹', label: { en: 'Dead Code', zh: '清理死代码' }, prefix: { en: 'Find and safely remove dead code, unused exports, and stale fixtures in: ', zh: '查找并安全移除死代码、未使用的导出与失效测试用例:' } },
30
+ { id: 'perf', icon: '⚙️', label: { en: 'Performance', zh: '性能优化' }, prefix: { en: 'Profile performance bottlenecks, eliminate unnecessary blocking calls in: ', 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,8 +870,8 @@ 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()}`
760
- : 'Токены ещё не зафиксированы';
873
+ ? `Prompt: ${promptTokens.toLocaleString('en-US')} | Completion: ${compTokens.toLocaleString('en-US')} | Total: ${totalTokens.toLocaleString('en-US')}`
874
+ : 'Tokens not recorded yet';
761
875
 
762
876
  const handleCopyReport = () => {
763
877
  const md = generateMarkdownReport(state);
@@ -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') || '💡 Clarify task or steer agent...',
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') || '✅ Steering sent!') : (t('sendNudge') || 'Steer'),
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') || 'Click to copy git diff command',
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 copied!' : `📌 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
- 'button',
884
- {
885
- className: `dsh-goal-btn dsh-goal-btn-copy${copied ? ' copied' : ''}`,
886
- onClick: handleCopyReport,
887
- title: t('copyReport') || 'Скопировать отчёт в Markdown',
888
- },
889
- copied ? (t('reportCopied') || '✅ Скопировано!') : (t('copyReport') || '📋 Скопировать отчёт в Markdown'),
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') || 'Copy Report in Markdown',
1066
+ },
1067
+ copied ? (t('reportCopied') || '✅ Copied!') : (t('copyReport') || '📋 Copy Report in 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: 'Copy Report for GitHub / Gitea PR Comment',
1083
+ },
1084
+ prCopied ? (t('prCommentCopied') || '✅ PR Report Copied!') : (t('copyPRComment') || '🐙 Copy for GitHub PR'),
1085
+ ),
890
1086
  )
891
1087
  : null,
892
1088
  isCompleted
@@ -899,7 +1095,7 @@ window.__ModuleLoader__.load({
899
1095
  onClose();
900
1096
  },
901
1097
  },
902
- t('closeBanner') || 'Закрыть плашку цели',
1098
+ t('closeBanner') || 'Close goal banner',
903
1099
  )
904
1100
  : null,
905
1101
  React.createElement(
@@ -908,7 +1104,7 @@ window.__ModuleLoader__.load({
908
1104
  className: 'dsh-goal-btn dsh-goal-btn-primary',
909
1105
  onClick: onClose,
910
1106
  },
911
- t('close') || 'Закрыть',
1107
+ t('close') || 'Close',
912
1108
  ),
913
1109
  ),
914
1110
  ),
@@ -949,11 +1145,11 @@ window.__ModuleLoader__.load({
949
1145
  'div',
950
1146
  { className: 'dsh-goal-modal-title' },
951
1147
  React.createElement(IconTarget, { size: 18 }),
952
- t('quickLaunchTitle') || 'Быстрый запуск цели',
1148
+ t('quickLaunchTitle') || 'Quick Launch Goal',
953
1149
  ),
954
1150
  React.createElement(
955
1151
  'button',
956
- { className: 'dsh-goal-btn icon-only close', onClick: onClose, title: t('close') || 'Закрыть' },
1152
+ { className: 'dsh-goal-btn icon-only close', onClick: onClose, title: t('close') || 'Close' },
957
1153
  '✕',
958
1154
  ),
959
1155
  ),
@@ -966,14 +1162,37 @@ window.__ModuleLoader__.load({
966
1162
  React.createElement(
967
1163
  'div',
968
1164
  { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)', marginBottom: 8 } },
969
- t('quickLaunchDesc') || 'Сформулируйте задачу для агента в автономном режиме:',
1165
+ t('quickLaunchDesc') || 'Define the objective for the agent in autonomous mode:',
1166
+ ),
1167
+ React.createElement(
1168
+ 'div',
1169
+ { className: 'dsh-goal-template-chips' },
1170
+ QUICK_LAUNCH_TEMPLATES.map((tmpl) => {
1171
+ const lang = t('localeCode') || 'en';
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
+ }),
970
1189
  ),
971
1190
  React.createElement('input', {
972
1191
  ref: inputRef,
973
1192
  type: 'text',
974
1193
  className: 'dsh-goal-card-input',
975
1194
  style: { width: '100%', boxSizing: 'border-box', fontSize: 13, padding: '8px 10px' },
976
- placeholder: t('quickLaunchPlaceholder') || 'Например: Реализовать экспорт отчетов и покрыть тестами...',
1195
+ placeholder: t('quickLaunchPlaceholder') || 'e.g. Implement report export and cover with unit tests...',
977
1196
  value: title,
978
1197
  onChange: (e) => setTitle(e.target.value),
979
1198
  onKeyDown: (e) => {
@@ -990,7 +1209,7 @@ window.__ModuleLoader__.load({
990
1209
  React.createElement(
991
1210
  'button',
992
1211
  { type: 'button', className: 'dsh-goal-btn', onClick: onClose },
993
- t('close') || 'Отмена',
1212
+ t('close') || 'Cancel',
994
1213
  ),
995
1214
  React.createElement(
996
1215
  'button',
@@ -999,7 +1218,7 @@ window.__ModuleLoader__.load({
999
1218
  className: 'dsh-goal-btn dsh-goal-btn-primary',
1000
1219
  disabled: !title.trim(),
1001
1220
  },
1002
- t('startGoal') || 'Запустить цель',
1221
+ t('startGoal') || 'Start Goal',
1003
1222
  ),
1004
1223
  ),
1005
1224
  ),
@@ -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 ? '🎯 Goal Completed' : '⚠️ Goal Failed',
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;
@@ -1274,11 +1509,11 @@ window.__ModuleLoader__.load({
1274
1509
  'button',
1275
1510
  {
1276
1511
  className: 'dsh-goal-quicklaunch-btn',
1277
- title: t('quickLaunch') || 'Запустить цель (Goal Mode)',
1512
+ title: t('quickLaunch') || 'Start Goal (Goal Mode)',
1278
1513
  onClick: () => setIsQuickLaunchOpen(true),
1279
1514
  },
1280
1515
  React.createElement(IconTarget, { size: 14 }),
1281
- React.createElement('span', null, t('quickLaunch') || 'Запустить цель'),
1516
+ React.createElement('span', null, t('quickLaunch') || 'Start Goal'),
1282
1517
  ),
1283
1518
  ),
1284
1519
  isQuickLaunchOpen
@@ -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
  };
@@ -1599,8 +1840,8 @@ window.__ModuleLoader__.load({
1599
1840
  React.createElement(
1600
1841
  'div',
1601
1842
  { style: { flex: 1 } },
1602
- React.createElement('div', { className: 'dsh-goal-card-title' }, t('pluginTitle') || 'Цели и автономный режим (Goal Mode)'),
1603
- React.createElement('div', { className: 'dsh-goal-card-sub' }, t('pluginDesc') || 'Верхняя закреплённая панель цели, декомпозиция вех и авто-драйв'),
1843
+ React.createElement('div', { className: 'dsh-goal-card-title' }, t('pluginTitle') || 'Goal Mode & Autonomous Loop'),
1844
+ React.createElement('div', { className: 'dsh-goal-card-sub' }, t('pluginDesc') || 'Goal banner above composer dock, milestone decomposition, and auto-drive'),
1604
1845
  ),
1605
1846
  React.createElement(Chevron, { className: `dsh-goal-chev ${isOpen ? 'dsh-goal-chev-open' : ''}` }),
1606
1847
  ),
@@ -1609,9 +1850,9 @@ window.__ModuleLoader__.load({
1609
1850
  'div',
1610
1851
  { className: 'dsh-goal-card-body' },
1611
1852
  status === 'loading'
1612
- ? React.createElement('p', { className: 'dsh-goal-field-hint', style: { padding: '12px 0' } }, t('loading') || 'Загрузка настроек…')
1853
+ ? React.createElement('p', { className: 'dsh-goal-field-hint', style: { padding: '12px 0' } }, t('loading') || 'Loading settings…')
1613
1854
  : status !== 'ready'
1614
- ? React.createElement('p', { className: 'dsh-goal-foot-note', style: { padding: '12px 0' } }, t('unavailable') || 'Настройки недоступны')
1855
+ ? React.createElement('p', { className: 'dsh-goal-foot-note', style: { padding: '12px 0' } }, t('unavailable') || 'Settings unavailable')
1615
1856
  : React.createElement(
1616
1857
  React.Fragment,
1617
1858
  null,
@@ -1621,12 +1862,12 @@ window.__ModuleLoader__.load({
1621
1862
  React.createElement(
1622
1863
  'div',
1623
1864
  { className: 'dsh-goal-field-label-row' },
1624
- React.createElement('label', { htmlFor: 'dsh-goal-max-iter' }, t('maxIterLabel') || 'Максимум итераций (Safety Limit):'),
1865
+ React.createElement('label', { htmlFor: 'dsh-goal-max-iter' }, t('maxIterLabel') || 'Max iterations (Safety Limit):'),
1625
1866
  React.createElement(
1626
1867
  'div',
1627
1868
  { className: 'dsh-goal-field-meta' },
1628
1869
  maxIterStatus.isOverridden
1629
- ? React.createElement('span', { className: 'dsh-goal-override-tag' }, t('overridden') || 'изменено')
1870
+ ? React.createElement('span', { className: 'dsh-goal-override-tag' }, t('overridden') || 'overridden')
1630
1871
  : null,
1631
1872
  maxIterStatus.isOverridden
1632
1873
  ? React.createElement(
@@ -1634,11 +1875,11 @@ window.__ModuleLoader__.load({
1634
1875
  {
1635
1876
  type: 'button',
1636
1877
  className: 'dsh-goal-btn-inline-reset',
1637
- title: t('resetField') || 'Сбросить к значению по умолчанию',
1878
+ title: t('resetField') || 'Reset to default',
1638
1879
  onClick: () => resetFieldToDefault('maxIterations'),
1639
1880
  },
1640
1881
  React.createElement(IconRotateCcw, { size: 12 }),
1641
- t('resetField') || 'По умолчанию',
1882
+ t('resetField') || 'Default',
1642
1883
  )
1643
1884
  : null,
1644
1885
  ),
@@ -1667,7 +1908,7 @@ window.__ModuleLoader__.load({
1667
1908
  disabled,
1668
1909
  onChange: (e) => edit('autoDrive', e.target.checked),
1669
1910
  }),
1670
- t('autoDriveLabel') || 'Авто-драйв: продолжать цикл автоматически',
1911
+ t('autoDriveLabel') || 'Auto-drive: keep the loop running automatically',
1671
1912
  ),
1672
1913
  autoDriveStatus.isOverridden
1673
1914
  ? React.createElement(
@@ -1675,7 +1916,7 @@ window.__ModuleLoader__.load({
1675
1916
  {
1676
1917
  type: 'button',
1677
1918
  className: 'dsh-goal-btn-inline-reset',
1678
- title: t('resetField') || 'Сбросить к значению по умолчанию',
1919
+ title: t('resetField') || 'Reset to default',
1679
1920
  onClick: () => resetFieldToDefault('autoDrive'),
1680
1921
  },
1681
1922
  React.createElement(IconRotateCcw, { size: 12 }),
@@ -1694,7 +1935,7 @@ window.__ModuleLoader__.load({
1694
1935
  disabled,
1695
1936
  onChange: (e) => edit('enableSound', e.target.checked),
1696
1937
  }),
1697
- t('soundLabel') || 'Звук по завершении цели',
1938
+ t('soundLabel') || 'Sound when a goal completes',
1698
1939
  ),
1699
1940
  enableSoundStatus.isOverridden
1700
1941
  ? React.createElement(
@@ -1702,7 +1943,7 @@ window.__ModuleLoader__.load({
1702
1943
  {
1703
1944
  type: 'button',
1704
1945
  className: 'dsh-goal-btn-inline-reset',
1705
- title: t('resetField') || 'Сбросить к значению по умолчанию',
1946
+ title: t('resetField') || 'Reset to default',
1706
1947
  onClick: () => resetFieldToDefault('enableSound'),
1707
1948
  },
1708
1949
  React.createElement(IconRotateCcw, { size: 12 }),
@@ -1721,7 +1962,7 @@ window.__ModuleLoader__.load({
1721
1962
  disabled,
1722
1963
  onChange: (e) => edit('showQuickLaunchButton', e.target.checked),
1723
1964
  }),
1724
- t('quickLaunchLabel') || 'Кнопка быстрого запуска цели над полем ввода',
1965
+ t('quickLaunchLabel') || 'Quick launch goal button above composer dock',
1725
1966
  ),
1726
1967
  quickLaunchStatus.isOverridden
1727
1968
  ? React.createElement(
@@ -1729,18 +1970,94 @@ window.__ModuleLoader__.load({
1729
1970
  {
1730
1971
  type: 'button',
1731
1972
  className: 'dsh-goal-btn-inline-reset',
1732
- title: t('resetField') || 'Сбросить к значению по умолчанию',
1973
+ title: t('resetField') || 'Reset to default',
1733
1974
  onClick: () => resetFieldToDefault('showQuickLaunchButton'),
1734
1975
  },
1735
1976
  React.createElement(IconRotateCcw, { size: 12 }),
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 Limit:'),
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') || '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') || 'Reset to default',
2000
+ onClick: () => edit('consecutiveToolFailureLimit', snap?.base?.consecutiveToolFailureLimit ?? DEFAULT_SETTINGS.consecutiveToolFailureLimit),
2001
+ },
2002
+ React.createElement(IconRotateCcw, { size: 12 }),
2003
+ t('resetField') || 'Default',
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') || 'Auto-pause goal if N consecutive turns hit tool execution errors (0 to disable)',
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') || 'Native browser desktop notifications on completion',
2042
+ ),
2043
+ notifStatus.isOverridden
2044
+ ? React.createElement(
2045
+ 'button',
2046
+ {
2047
+ type: 'button',
2048
+ className: 'dsh-goal-btn-inline-reset',
2049
+ title: t('resetField') || 'Reset to default',
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' },
1742
2059
  failed
1743
- ? React.createElement('span', { className: 'dsh-goal-foot-note' }, t('saveFailed') || 'Не сохраненоисправьте и повторите')
2060
+ ? React.createElement('span', { className: 'dsh-goal-foot-note' }, t('saveFailed') || 'Not savedfix the values and try again')
1744
2061
  : null,
1745
2062
  React.createElement(
1746
2063
  'button',
@@ -1752,12 +2069,12 @@ window.__ModuleLoader__.load({
1752
2069
  setDraft(null);
1753
2070
  },
1754
2071
  },
1755
- t('discard') || 'Отменить правки',
2072
+ t('discard') || 'Discard changes',
1756
2073
  ),
1757
2074
  React.createElement(
1758
2075
  'button',
1759
2076
  { className: 'dsh-goal-save', disabled: !dirty || disabled || invalid, onClick: save },
1760
- saving ? t('saving') || 'Сохранение…' : t('save') || 'Сохранить',
2077
+ saving ? (t('saving') || 'Saving…') : (t('save') || 'Save'),
1761
2078
  ),
1762
2079
  ),
1763
2080
  ),
@@ -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,50 +2178,18 @@ 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
- ru: {
1856
- goalLabel: 'Текущая цель',
1857
- goalCompleted: 'Цель выполнена',
1858
- clearGoal: 'Очистить цель',
1859
- closeBanner: 'Закрыть плашку цели',
1860
- pause: 'Приостановить',
1861
- paused: 'На паузе',
1862
- resume: 'Возобновить',
1863
- details: 'Развернуть детали цели',
1864
- modalTitle: 'План и статус цели',
1865
- modalTitleCompleted: 'Цель выполнена: план и результаты',
1866
- time: 'Время работы',
1867
- eta: 'Оценка (ETA)',
1868
- tokens: 'Токены',
1869
- iterations: 'Итерации',
1870
- progress: 'Прогресс',
1871
- done: 'Готово',
1872
- milestones: 'План работ:',
1873
- noMilestones: 'Агент формирует план работ...',
1874
- close: 'Закрыть',
1875
- pluginTitle: 'Цели и автономный режим (Goal Mode)',
1876
- pluginDesc: 'Панель цели над полем ввода, декомпозиция вех и авто-драйв',
1877
- maxIterLabel: 'Максимум итераций (Safety Limit):',
1878
- autoDriveLabel: 'Авто-драйв: продолжать цикл автоматически',
1879
- soundLabel: 'Звук по завершении цели',
1880
- quickLaunchLabel: 'Кнопка быстрого запуска цели над полем ввода',
1881
- quickLaunch: 'Запустить цель',
1882
- quickLaunchTitle: 'Быстрый запуск цели',
1883
- quickLaunchDesc: 'Сформулируйте задачу для агента в автономном режиме:',
1884
- quickLaunchPlaceholder: 'Например: Реализовать экспорт отчетов и покрыть тестами...',
1885
- startGoal: 'Запустить цель',
1886
- copyReport: '📋 Скопировать отчёт в Markdown',
1887
- reportCopied: '✅ Скопировано!',
1888
- save: 'Сохранить',
1889
- saving: 'Сохранение…',
1890
- discard: 'Отменить правки',
1891
- resetField: 'По умолчанию',
1892
- overridden: 'изменено',
1893
- saveFailed: 'Не сохранено — исправьте и повторите',
1894
- loading: 'Загрузка настроек…',
1895
- unavailable: 'Настройки недоступны',
1896
- },
1897
- };
2192
+ };
1898
2193
 
1899
2194
  module.exports.inject = ['slots', 'locale', 'settingsScope'];
1900
2195
  module.exports.apply = function apply(ctx) {