@goodandready/dsh-goal 0.2.0 → 0.2.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/lib/client.js CHANGED
@@ -1,2301 +1,4498 @@
1
1
  window.__ModuleLoader__.load({
2
+
2
3
  id: '@goodandready/dsh-goal',
4
+
3
5
  factory: (require) => {
6
+
4
7
  const module = { exports: {} };
8
+
5
9
  const React = require('react');
10
+
6
11
  const { useState, useEffect, useRef, useCallback, useMemo } = React;
7
12
 
13
+
14
+
8
15
  const NS = 'dsh-goal';
9
16
 
17
+
18
+
10
19
  // Безопасная загрузка примитивов ядра
20
+
11
21
  let Primitives = null;
22
+
12
23
  try {
24
+
13
25
  Primitives = require('@deepseek-ai/dsh-client-ui-primitives');
26
+
14
27
  } catch (_) {
28
+
15
29
  Primitives = null;
30
+
16
31
  }
17
32
 
18
33
 
34
+
35
+
36
+
19
37
  // --- 10 ИНЖЕНЕРНЫХ ШАБЛОНОВ БЫСТРОГО ЗАПУСКА ЦЕЛИ ---
38
+
20
39
  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: '分析性能瓶颈,消除阻塞调用并优化响应速度:' } },
40
+
41
+ { id: 'fix', icon: '🐛', label: { en: 'Fix Bug', zh: '修复缺陷' }, prefix: { en: 'Investigate bug, pinpoint root cause, apply fix, and verify with tests: ', zh: '调查并定位缺陷根源,实施修复并通过测试验证:' } },
42
+
43
+ { id: 'refactor', icon: '', label: { en: 'Refactor (YAGNI)', zh: '重构精简' }, prefix: { en: 'Refactor module applying YAGNI principle, delete redundant abstractions, and verify: ', zh: '遵循 YAGNI 原则重构模块,删除冗余抽象并验证:' } },
44
+
45
+ { id: 'tests', icon: '📝', label: { en: 'Tests & Coverage', zh: '补充测试' }, prefix: { en: 'Write comprehensive unit tests covering edge cases for: ', zh: '为以下模块编写覆盖边界情况的完整单元测试:' } },
46
+
47
+ { id: 'review', icon: '🔍', label: { en: 'Code Review', zh: '代码审计' }, prefix: { en: 'Review codebase for subtle bugs, memory leaks, and over-engineering in: ', zh: '对代码库进行深入审查,排查潜在缺陷与过度设计:' } },
48
+
49
+ { id: 'feature', icon: '🚀', label: { en: 'New Feature', zh: '新功能开发' }, prefix: { en: 'Implement new feature according to specification and add tests for: ', zh: '根据规格说明实现新功能并补充测试:' } },
50
+
51
+ { id: 'security', icon: '🛡️', label: { en: 'Security Audit', zh: '安全加固' }, prefix: { en: 'Audit security boundaries, sanitize inputs, and prevent injection in: ', zh: '审计安全边界,增加输入过滤与防注入保护:' } },
52
+
53
+ { 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:' } },
54
+
55
+ { id: 'upgrade', icon: '📦', label: { en: 'Upgrade Deps', zh: '依赖升级' }, prefix: { en: 'Upgrade dependencies, resolve breaking API changes, and verify build for: ', zh: '安全升级依赖库,解决接口兼容性并确保构建通过:' } },
56
+
57
+ { id: 'cleanup', icon: '🧹', label: { en: 'Dead Code', zh: '清理死代码' }, prefix: { en: 'Find and safely remove dead code, unused exports, and stale fixtures in: ', zh: '查找并安全移除死代码、未使用的导出与失效测试用例:' } },
58
+
59
+ { id: 'perf', icon: '⚙️', label: { en: 'Performance', zh: '性能优化' }, prefix: { en: 'Profile performance bottlenecks, eliminate unnecessary blocking calls in: ', zh: '分析性能瓶颈,消除阻塞调用并优化响应速度:' } },
60
+
31
61
  ];
32
62
 
63
+
64
+
33
65
  // --- SVG ИКОНКИ ---
34
66
 
67
+
68
+
35
69
  function IconTarget({ size = 15, className = '' }) {
70
+
36
71
  return React.createElement(
72
+
37
73
  'svg',
74
+
38
75
  {
76
+
39
77
  width: size,
78
+
40
79
  height: size,
80
+
41
81
  viewBox: '0 0 24 24',
82
+
42
83
  fill: 'none',
84
+
43
85
  stroke: 'currentColor',
86
+
44
87
  strokeWidth: 2,
88
+
45
89
  strokeLinecap: 'round',
90
+
46
91
  strokeLinejoin: 'round',
92
+
47
93
  className,
94
+
48
95
  },
96
+
49
97
  React.createElement('circle', { cx: 12, cy: 12, r: 10 }),
98
+
50
99
  React.createElement('circle', { cx: 12, cy: 12, r: 6 }),
100
+
51
101
  React.createElement('circle', { cx: 12, cy: 12, r: 2 }),
102
+
52
103
  );
104
+
53
105
  }
54
106
 
107
+
108
+
55
109
  function IconTrash({ size = 14, className = '' }) {
110
+
56
111
  return React.createElement(
112
+
57
113
  'svg',
114
+
58
115
  {
116
+
59
117
  width: size,
118
+
60
119
  height: size,
120
+
61
121
  viewBox: '0 0 24 24',
122
+
62
123
  fill: 'none',
124
+
63
125
  stroke: 'currentColor',
126
+
64
127
  strokeWidth: 1.8,
128
+
65
129
  strokeLinecap: 'round',
130
+
66
131
  strokeLinejoin: 'round',
132
+
67
133
  className,
134
+
68
135
  },
136
+
69
137
  React.createElement('path', { d: 'M3 6h18' }),
138
+
70
139
  React.createElement('path', { d: 'M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6' }),
140
+
71
141
  React.createElement('path', { d: 'M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2' }),
142
+
72
143
  );
144
+
73
145
  }
74
146
 
147
+
148
+
75
149
  function IconPause({ size = 14, className = '' }) {
150
+
76
151
  return React.createElement(
152
+
77
153
  'svg',
154
+
78
155
  {
156
+
79
157
  width: size,
158
+
80
159
  height: size,
160
+
81
161
  viewBox: '0 0 24 24',
162
+
82
163
  fill: 'none',
164
+
83
165
  stroke: 'currentColor',
166
+
84
167
  strokeWidth: 1.8,
168
+
85
169
  strokeLinecap: 'round',
170
+
86
171
  strokeLinejoin: 'round',
172
+
87
173
  className,
174
+
88
175
  },
176
+
89
177
  React.createElement('circle', { cx: 12, cy: 12, r: 10 }),
178
+
90
179
  React.createElement('line', { x1: 10, y1: 15, x2: 10, y2: 9 }),
180
+
91
181
  React.createElement('line', { x1: 14, y1: 15, x2: 14, y2: 9 }),
182
+
92
183
  );
184
+
93
185
  }
94
186
 
187
+
188
+
95
189
  function IconPlay({ size = 14, className = '' }) {
190
+
96
191
  return React.createElement(
192
+
97
193
  'svg',
194
+
98
195
  {
196
+
99
197
  width: size,
198
+
100
199
  height: size,
200
+
101
201
  viewBox: '0 0 24 24',
202
+
102
203
  fill: 'none',
204
+
103
205
  stroke: 'currentColor',
206
+
104
207
  strokeWidth: 1.8,
208
+
105
209
  strokeLinecap: 'round',
210
+
106
211
  strokeLinejoin: 'round',
212
+
107
213
  className,
214
+
108
215
  },
216
+
109
217
  React.createElement('circle', { cx: 12, cy: 12, r: 10 }),
218
+
110
219
  React.createElement('polygon', { points: '10 8 16 12 10 16 10 8', fill: 'currentColor' }),
220
+
111
221
  );
222
+
112
223
  }
113
224
 
225
+
226
+
114
227
  function IconCheck({ size = 15, className = '' }) {
228
+
115
229
  return React.createElement(
230
+
116
231
  'svg',
232
+
117
233
  {
234
+
118
235
  width: size,
236
+
119
237
  height: size,
238
+
120
239
  viewBox: '0 0 24 24',
240
+
121
241
  fill: 'none',
242
+
122
243
  stroke: 'currentColor',
244
+
123
245
  strokeWidth: 2.5,
246
+
124
247
  strokeLinecap: 'round',
248
+
125
249
  strokeLinejoin: 'round',
250
+
126
251
  className,
252
+
127
253
  },
254
+
128
255
  React.createElement('polyline', { points: '20 6 9 17 4 12' }),
256
+
129
257
  );
258
+
130
259
  }
131
260
 
261
+
262
+
132
263
  function IconExpand({ size = 14, className = '' }) {
264
+
133
265
  return React.createElement(
266
+
134
267
  'svg',
268
+
135
269
  {
270
+
136
271
  width: size,
272
+
137
273
  height: size,
274
+
138
275
  viewBox: '0 0 24 24',
276
+
139
277
  fill: 'none',
278
+
140
279
  stroke: 'currentColor',
280
+
141
281
  strokeWidth: 1.8,
282
+
142
283
  strokeLinecap: 'round',
284
+
143
285
  strokeLinejoin: 'round',
286
+
144
287
  className,
288
+
145
289
  },
290
+
146
291
  React.createElement('polyline', { points: '15 3 21 3 21 9' }),
292
+
147
293
  React.createElement('polyline', { points: '9 21 3 21 3 15' }),
294
+
148
295
  React.createElement('line', { x1: 21, y1: 3, x2: 14, y2: 10 }),
296
+
149
297
  React.createElement('line', { x1: 3, y1: 21, x2: 10, y2: 14 }),
298
+
150
299
  );
300
+
151
301
  }
152
302
 
303
+
304
+
153
305
  function IconRotateCcw({ size = 13, className = '' }) {
306
+
154
307
  return React.createElement(
308
+
155
309
  'svg',
310
+
156
311
  {
312
+
157
313
  width: size,
314
+
158
315
  height: size,
316
+
159
317
  viewBox: '0 0 24 24',
318
+
160
319
  fill: 'none',
320
+
161
321
  stroke: 'currentColor',
322
+
162
323
  strokeWidth: 2,
324
+
163
325
  strokeLinecap: 'round',
326
+
164
327
  strokeLinejoin: 'round',
328
+
165
329
  className,
330
+
166
331
  },
332
+
167
333
  React.createElement('polyline', { points: '1 4 1 10 7 10' }),
334
+
168
335
  React.createElement('path', { d: 'M3.51 15a9 9 0 1 0 2.13-9.36L1 10' }),
336
+
169
337
  );
338
+
170
339
  }
171
340
 
341
+
342
+
172
343
  function IconChevronDown({ size = 14, className = '' }) {
344
+
173
345
  return React.createElement(
346
+
174
347
  'svg',
348
+
175
349
  {
350
+
176
351
  width: size,
352
+
177
353
  height: size,
354
+
178
355
  viewBox: '0 0 24 24',
356
+
179
357
  fill: 'none',
358
+
180
359
  stroke: 'currentColor',
360
+
181
361
  strokeWidth: 2,
362
+
182
363
  strokeLinecap: 'round',
364
+
183
365
  strokeLinejoin: 'round',
366
+
184
367
  className,
368
+
185
369
  },
370
+
186
371
  React.createElement('polyline', { points: '6 9 12 15 18 9' }),
372
+
187
373
  );
374
+
188
375
  }
189
376
 
377
+
378
+
190
379
  // --- СТИЛИ И CSS ПРАВИЛА (СТАНДАРТ DSH-CLINEBOT & ДИЗАЙН-СИСТЕМА DSH) ---
380
+
191
381
  const CSS_STYLES = `
382
+
192
383
  .dsh-goal-dock {
384
+
193
385
  box-sizing: border-box;
386
+
194
387
  width: calc(100% - var(--dsh-composer-side-clearance, 0px) * 2 - var(--dsh-composer-dock-inset, 0px) * 4);
388
+
195
389
  max-width: calc(var(--dsh-composer-card-max-width, 768px) - var(--dsh-composer-dock-inset, 0px) * 4);
390
+
196
391
  margin: 0 auto 8px auto;
392
+
197
393
  display: flex;
394
+
198
395
  justify-content: center;
396
+
199
397
  }
398
+
200
399
  .dsh-goal-dock.dsh-goal-dock-quick {
400
+
201
401
  justify-content: flex-start;
402
+
202
403
  margin-bottom: 4px;
404
+
203
405
  }
406
+
204
407
 
408
+
205
409
  .dsh-goal-template-chips {
410
+
206
411
  display: flex;
412
+
207
413
  flex-wrap: wrap;
414
+
208
415
  gap: 6px;
416
+
209
417
  margin-bottom: 12px;
418
+
210
419
  }
420
+
211
421
  .dsh-goal-template-chip {
422
+
212
423
  appearance: none;
424
+
213
425
  font: inherit;
426
+
214
427
  background: var(--dsw-alias-bg-layer-2);
428
+
215
429
  border: 1px solid var(--dsw-alias-border-l2);
430
+
216
431
  border-radius: 999px;
432
+
217
433
  padding: 3px 8px;
434
+
218
435
  font-size: 11px;
436
+
219
437
  color: var(--dsw-alias-label-secondary);
438
+
220
439
  cursor: pointer;
440
+
221
441
  display: inline-flex;
442
+
222
443
  align-items: center;
444
+
223
445
  gap: 4px;
446
+
224
447
  transition: all 0.12s ease;
448
+
225
449
  }
450
+
226
451
  .dsh-goal-template-chip:hover {
452
+
227
453
  background: var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-3));
454
+
228
455
  border-color: var(--dsw-alias-label-tertiary);
456
+
229
457
  color: var(--dsw-alias-label-primary);
458
+
230
459
  }
460
+
231
461
  .dsh-goal-nudge-box {
462
+
232
463
  display: flex;
464
+
233
465
  align-items: center;
466
+
234
467
  gap: 8px;
468
+
235
469
  margin-top: 4px;
470
+
236
471
  }
472
+
237
473
  .dsh-goal-git-badge {
474
+
238
475
  display: inline-flex;
476
+
239
477
  align-items: center;
478
+
240
479
  gap: 4px;
480
+
241
481
  font-family: monospace;
482
+
242
483
  font-size: 11px;
484
+
243
485
  padding: 2px 6px;
486
+
244
487
  border-radius: 4px;
488
+
245
489
  background: var(--dsw-alias-bg-layer-2);
490
+
246
491
  border: 1px solid var(--dsw-alias-border-l2);
492
+
247
493
  color: var(--dsw-alias-label-secondary);
494
+
248
495
  cursor: pointer;
496
+
249
497
  }
498
+
250
499
  .dsh-goal-git-badge:hover {
500
+
251
501
  color: var(--dsw-alias-label-primary);
502
+
252
503
  border-color: var(--dsw-alias-label-tertiary);
504
+
253
505
  }
506
+
254
507
  .dsh-goal-quicklaunch-btn {
508
+
255
509
  appearance: none;
510
+
256
511
  font: inherit;
512
+
257
513
  background: var(--dsw-alias-bg-layer-2);
514
+
258
515
  border: 1px solid var(--dsw-alias-border-l2);
516
+
259
517
  border-radius: 999px;
518
+
260
519
  padding: 4px 11px;
520
+
261
521
  color: var(--dsw-alias-label-secondary);
522
+
262
523
  font-size: 12px;
524
+
263
525
  font-weight: 500;
526
+
264
527
  cursor: pointer;
528
+
265
529
  display: inline-flex;
530
+
266
531
  align-items: center;
532
+
267
533
  gap: 6px;
534
+
268
535
  transition: all 0.15s ease;
536
+
269
537
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
538
+
270
539
  }
540
+
271
541
  .dsh-goal-quicklaunch-btn:hover {
542
+
272
543
  background: var(--dsw-alias-bg-layer-3);
544
+
273
545
  border-color: var(--dsw-alias-label-tertiary);
546
+
274
547
  color: var(--dsw-alias-label-primary);
548
+
275
549
  }
550
+
276
551
  .dsh-goal-banner {
552
+
277
553
  box-sizing: border-box;
554
+
278
555
  width: 100%;
556
+
279
557
  display: flex;
558
+
280
559
  align-items: center;
560
+
281
561
  justify-content: space-between;
562
+
282
563
  background: var(--dsw-alias-bg-layer-3);
564
+
283
565
  border: 1px solid var(--dsw-alias-border-l2);
566
+
284
567
  border-radius: 12px;
568
+
285
569
  padding: 7px 14px;
570
+
286
571
  font-family: inherit;
572
+
287
573
  font-size: 13px;
574
+
288
575
  color: var(--dsw-alias-label-primary);
576
+
289
577
  user-select: none;
578
+
290
579
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
580
+
291
581
  transition: all 0.15s ease;
582
+
292
583
  }
584
+
293
585
  .dsh-goal-banner:hover {
586
+
294
587
  border-color: var(--dsw-alias-label-tertiary);
588
+
295
589
  }
590
+
296
591
  .dsh-goal-banner.completed {
592
+
297
593
  border-color: var(--dsw-alias-state-success-primary);
594
+
298
595
  background: var(--dsw-alias-bg-layer-3);
596
+
299
597
  }
598
+
300
599
  .dsh-goal-left {
600
+
301
601
  display: flex;
602
+
302
603
  align-items: center;
604
+
303
605
  gap: 8px;
606
+
304
607
  min-width: 0;
608
+
305
609
  flex: 1;
610
+
306
611
  }
612
+
307
613
  .dsh-goal-icon {
614
+
308
615
  color: var(--dsw-alias-label-secondary);
616
+
309
617
  flex-shrink: 0;
618
+
310
619
  display: flex;
620
+
311
621
  align-items: center;
622
+
312
623
  }
624
+
313
625
  .dsh-goal-badge {
626
+
314
627
  font-size: 11px;
628
+
315
629
  padding: 2px 8px;
630
+
316
631
  border-radius: 999px;
632
+
317
633
  border: 1px solid var(--dsw-alias-border-l2);
634
+
318
635
  display: inline-flex;
636
+
319
637
  align-items: center;
638
+
320
639
  gap: 4px;
640
+
321
641
  font-weight: 600;
642
+
322
643
  white-space: nowrap;
644
+
323
645
  background: var(--dsw-alias-bg-layer-2);
646
+
324
647
  color: var(--dsw-alias-label-primary);
648
+
325
649
  }
650
+
326
651
  .dsh-goal-badge-ok {
652
+
327
653
  border-color: var(--dsw-alias-state-success-primary);
654
+
328
655
  color: var(--dsw-alias-state-success-primary);
656
+
329
657
  background: rgba(16, 185, 129, 0.08);
658
+
330
659
  }
660
+
331
661
  .dsh-goal-badge-warn {
662
+
332
663
  border-color: var(--dsw-alias-state-warning-primary);
664
+
333
665
  color: var(--dsw-alias-state-warning-primary);
666
+
334
667
  background: rgba(245, 158, 11, 0.08);
668
+
335
669
  }
670
+
336
671
  .dsh-goal-label {
672
+
337
673
  font-weight: 600;
674
+
338
675
  color: var(--dsw-alias-label-primary);
676
+
339
677
  white-space: nowrap;
678
+
340
679
  }
680
+
341
681
  .dsh-goal-label.completed {
682
+
342
683
  color: var(--dsw-alias-state-success-primary);
684
+
343
685
  }
686
+
344
687
  .dsh-goal-title {
688
+
345
689
  color: var(--dsw-alias-label-secondary);
690
+
346
691
  overflow: hidden;
692
+
347
693
  text-overflow: ellipsis;
694
+
348
695
  white-space: nowrap;
696
+
349
697
  font-weight: 400;
698
+
350
699
  flex: 1;
700
+
351
701
  min-width: 0;
702
+
352
703
  }
704
+
353
705
  .dsh-goal-time {
706
+
354
707
  color: var(--dsw-alias-label-secondary);
708
+
355
709
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
710
+
356
711
  font-size: 12px;
712
+
357
713
  font-weight: 500;
714
+
358
715
  white-space: nowrap;
716
+
359
717
  display: inline-flex;
718
+
360
719
  align-items: center;
720
+
361
721
  gap: 4px;
722
+
362
723
  flex-shrink: 0;
724
+
363
725
  }
726
+
364
727
  .dsh-goal-right {
728
+
365
729
  display: flex;
730
+
366
731
  align-items: center;
732
+
367
733
  gap: 8px;
734
+
368
735
  margin-left: 12px;
736
+
369
737
  flex-shrink: 0;
738
+
370
739
  }
740
+
371
741
  .dsh-goal-btn {
742
+
372
743
  appearance: none;
744
+
373
745
  font: inherit;
746
+
374
747
  background: var(--dsw-alias-bg-layer-2);
748
+
375
749
  border: 1px solid var(--dsw-alias-border-l2);
750
+
376
751
  padding: 5px 8px;
752
+
377
753
  margin: 0;
754
+
378
755
  color: var(--dsw-alias-label-primary);
756
+
379
757
  cursor: pointer;
758
+
380
759
  display: inline-flex;
760
+
381
761
  align-items: center;
762
+
382
763
  justify-content: center;
764
+
383
765
  gap: 5px;
766
+
384
767
  border-radius: 8px;
768
+
385
769
  font-size: 12px;
770
+
386
771
  font-weight: 500;
772
+
387
773
  transition: all 0.15s ease;
774
+
388
775
  }
776
+
389
777
  .dsh-goal-btn:hover:not(:disabled) {
778
+
390
779
  background: var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-2));
780
+
391
781
  border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2));
782
+
392
783
  }
784
+
393
785
  .dsh-goal-btn.icon-only {
786
+
394
787
  padding: 4px 6px;
788
+
395
789
  border-radius: 6px;
790
+
396
791
  border-color: transparent;
792
+
397
793
  background: transparent;
794
+
398
795
  color: var(--dsw-alias-label-tertiary);
796
+
399
797
  }
798
+
400
799
  .dsh-goal-btn.icon-only:hover {
800
+
401
801
  color: var(--dsw-alias-label-primary);
802
+
402
803
  background: var(--dsw-alias-bg-layer-2);
804
+
403
805
  border-color: var(--dsw-alias-border-l2);
806
+
404
807
  }
808
+
405
809
  .dsh-goal-btn.close {
810
+
406
811
  font-size: 13px;
812
+
407
813
  font-weight: bold;
814
+
408
815
  padding: 3px 6px;
816
+
409
817
  }
818
+
410
819
  .dsh-goal-btn.close:hover {
820
+
411
821
  color: var(--dsw-alias-state-error-primary);
822
+
412
823
  }
824
+
413
825
  .dsh-goal-btn-primary {
826
+
414
827
  background: var(--dsw-alias-label-primary);
828
+
415
829
  color: var(--dsw-alias-bg-layer-3);
830
+
416
831
  border-color: transparent;
832
+
417
833
  }
834
+
418
835
  .dsh-goal-btn-primary:hover:not(:disabled) {
836
+
419
837
  opacity: 0.88;
838
+
420
839
  }
840
+
421
841
  .dsh-goal-btn-danger {
842
+
422
843
  color: var(--dsw-alias-state-error-primary);
844
+
423
845
  border-color: rgba(239, 68, 68, 0.3);
846
+
424
847
  }
848
+
425
849
  .dsh-goal-btn-danger:hover:not(:disabled) {
850
+
426
851
  background: rgba(239, 68, 68, 0.12) !important;
852
+
427
853
  border-color: rgba(239, 68, 68, 0.5);
854
+
428
855
  }
429
856
 
857
+
858
+
430
859
  /* Modal Details */
860
+
431
861
  .dsh-goal-modal-overlay {
862
+
432
863
  position: fixed;
864
+
433
865
  inset: 0;
866
+
434
867
  background: rgba(0, 0, 0, 0.6);
868
+
435
869
  display: flex;
870
+
436
871
  align-items: center;
872
+
437
873
  justify-content: center;
874
+
438
875
  z-index: 10000;
876
+
439
877
  backdrop-filter: blur(4px);
878
+
440
879
  }
880
+
441
881
  .dsh-goal-modal {
882
+
442
883
  background: var(--dsw-alias-bg-layer-3);
884
+
443
885
  border: 1px solid var(--dsw-alias-border-l2);
886
+
444
887
  border-radius: 12px;
888
+
445
889
  width: 560px;
890
+
446
891
  max-width: 90vw;
892
+
447
893
  max-height: 80vh;
894
+
448
895
  display: flex;
896
+
449
897
  flex-direction: column;
898
+
450
899
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.4);
900
+
451
901
  }
902
+
452
903
  .dsh-goal-modal-head {
904
+
453
905
  padding: 16px 20px;
906
+
454
907
  border-bottom: 1px solid var(--dsw-alias-border-l2);
908
+
455
909
  display: flex;
910
+
456
911
  align-items: center;
912
+
457
913
  justify-content: space-between;
914
+
458
915
  }
916
+
459
917
  .dsh-goal-modal-title {
918
+
460
919
  font-size: 16px;
920
+
461
921
  font-weight: 600;
922
+
462
923
  color: var(--dsw-alias-label-primary);
924
+
463
925
  display: flex;
926
+
464
927
  align-items: center;
928
+
465
929
  gap: 8px;
930
+
466
931
  }
932
+
467
933
  .dsh-goal-modal-body {
934
+
468
935
  padding: 18px 20px;
936
+
469
937
  overflow-y: auto;
938
+
470
939
  display: flex;
940
+
471
941
  flex-direction: column;
942
+
472
943
  gap: 14px;
944
+
473
945
  }
946
+
474
947
  .dsh-goal-stat-grid {
948
+
475
949
  display: grid;
950
+
476
951
  grid-template-columns: repeat(auto-fit, minmax(95px, 1fr));
952
+
477
953
  gap: 10px;
954
+
478
955
  }
956
+
479
957
  .dsh-goal-stat-box {
958
+
480
959
  padding: 10px 12px;
960
+
481
961
  border: 1px solid var(--dsw-alias-border-l2);
962
+
482
963
  border-radius: 8px;
964
+
483
965
  background: var(--dsw-alias-bg-layer-2);
966
+
484
967
  display: flex;
968
+
485
969
  flex-direction: column;
970
+
486
971
  gap: 2px;
972
+
487
973
  }
974
+
488
975
  .dsh-goal-stat-val {
976
+
489
977
  font-size: 15px;
978
+
490
979
  font-weight: 700;
980
+
491
981
  color: var(--dsw-alias-label-primary);
982
+
492
983
  overflow: hidden;
984
+
493
985
  text-overflow: ellipsis;
986
+
494
987
  white-space: nowrap;
988
+
495
989
  }
990
+
496
991
  .dsh-goal-stat-lbl {
992
+
497
993
  font-size: 11px;
994
+
498
995
  color: var(--dsw-alias-label-secondary);
996
+
499
997
  white-space: nowrap;
998
+
500
999
  }
1000
+
501
1001
  .dsh-goal-milestone-item {
1002
+
502
1003
  display: flex;
1004
+
503
1005
  align-items: flex-start;
1006
+
504
1007
  gap: 10px;
1008
+
505
1009
  padding: 10px 12px;
1010
+
506
1011
  border-radius: 8px;
1012
+
507
1013
  background: var(--dsw-alias-bg-layer-2);
1014
+
508
1015
  border: 1px solid var(--dsw-alias-border-l2);
1016
+
509
1017
  font-size: 13px;
1018
+
510
1019
  transition: all 0.15s ease;
1020
+
511
1021
  }
1022
+
512
1023
  .dsh-goal-modal-foot {
1024
+
513
1025
  padding: 14px 20px;
1026
+
514
1027
  border-top: 1px solid var(--dsw-alias-border-l2);
1028
+
515
1029
  display: flex;
1030
+
516
1031
  justify-content: flex-end;
1032
+
517
1033
  align-items: center;
1034
+
518
1035
  gap: 10px;
1036
+
519
1037
  flex-wrap: wrap;
1038
+
520
1039
  }
1040
+
521
1041
  .dsh-goal-btn-copy {
1042
+
522
1043
  background: var(--dsw-alias-bg-layer-2);
1044
+
523
1045
  border: 1px solid var(--dsw-alias-border-l2);
1046
+
524
1047
  color: var(--dsw-alias-label-primary);
1048
+
525
1049
  }
1050
+
526
1051
  .dsh-goal-btn-copy.copied {
1052
+
527
1053
  background: rgba(16, 185, 129, 0.12);
1054
+
528
1055
  border-color: var(--dsw-alias-state-success-primary);
1056
+
529
1057
  color: var(--dsw-alias-state-success-primary);
1058
+
530
1059
  }
531
1060
 
1061
+
1062
+
532
1063
  /* Settings Card (clinebot pattern) */
1064
+
533
1065
  .dsh-goal-card {
1066
+
534
1067
  border: 1px solid var(--dsw-alias-border-l2);
1068
+
535
1069
  background: var(--dsw-alias-bg-layer-3);
1070
+
536
1071
  border-radius: 12px;
1072
+
537
1073
  list-style: none;
1074
+
538
1075
  margin-bottom: 12px;
1076
+
539
1077
  transition: border-color 0.15s ease;
1078
+
540
1079
  }
1080
+
541
1081
  .dsh-goal-card:hover {
1082
+
542
1083
  border-color: var(--dsw-alias-label-tertiary);
1084
+
543
1085
  }
1086
+
544
1087
  .dsh-goal-card-head {
1088
+
545
1089
  appearance: none;
1090
+
546
1091
  width: 100%;
1092
+
547
1093
  font: inherit;
1094
+
548
1095
  color: inherit;
1096
+
549
1097
  text-align: left;
1098
+
550
1099
  cursor: pointer;
1100
+
551
1101
  background: transparent;
1102
+
552
1103
  border: 0;
1104
+
553
1105
  border-radius: 12px;
1106
+
554
1107
  display: flex;
1108
+
555
1109
  align-items: center;
1110
+
556
1111
  gap: 12px;
1112
+
557
1113
  padding: 16px 20px;
1114
+
558
1115
  }
1116
+
559
1117
  .dsh-goal-card-title {
1118
+
560
1119
  color: var(--dsw-alias-label-primary);
1120
+
561
1121
  font-size: 15px;
1122
+
562
1123
  font-weight: 600;
1124
+
563
1125
  line-height: 1.4;
1126
+
564
1127
  }
1128
+
565
1129
  .dsh-goal-card-sub {
1130
+
566
1131
  color: var(--dsw-alias-label-secondary);
1132
+
567
1133
  font-size: 13px;
1134
+
568
1135
  }
1136
+
569
1137
  .dsh-goal-card-body {
1138
+
570
1139
  border-top: 1px solid var(--dsw-alias-border-l2);
1140
+
571
1141
  margin: 0 20px;
1142
+
572
1143
  padding: 16px 0 16px;
1144
+
573
1145
  display: flex;
1146
+
574
1147
  flex-direction: column;
1148
+
575
1149
  gap: 14px;
1150
+
576
1151
  }
1152
+
577
1153
  .dsh-goal-card-field {
1154
+
578
1155
  display: flex;
1156
+
579
1157
  flex-direction: column;
1158
+
580
1159
  gap: 6px;
1160
+
581
1161
  }
1162
+
582
1163
  .dsh-goal-field-label-row {
1164
+
583
1165
  display: flex;
1166
+
584
1167
  align-items: center;
1168
+
585
1169
  justify-content: space-between;
1170
+
586
1171
  font-size: 13px;
1172
+
587
1173
  font-weight: 500;
1174
+
588
1175
  color: var(--dsw-alias-label-primary);
1176
+
589
1177
  }
1178
+
590
1179
  .dsh-goal-field-meta {
1180
+
591
1181
  display: flex;
1182
+
592
1183
  align-items: center;
1184
+
593
1185
  gap: 6px;
1186
+
594
1187
  font-size: 11px;
1188
+
595
1189
  color: var(--dsw-alias-label-tertiary);
1190
+
596
1191
  }
1192
+
597
1193
  .dsh-goal-override-tag {
1194
+
598
1195
  font-size: 11px;
1196
+
599
1197
  padding: 2px 6px;
1198
+
600
1199
  border-radius: 4px;
1200
+
601
1201
  background: var(--dsw-alias-bg-layer-2);
1202
+
602
1203
  color: var(--dsw-alias-label-secondary);
1204
+
603
1205
  border: 1px solid var(--dsw-alias-border-l2);
1206
+
604
1207
  }
1208
+
605
1209
  .dsh-goal-card-input {
1210
+
606
1211
  height: 36px;
1212
+
607
1213
  border: 1px solid var(--dsw-alias-border-l2);
1214
+
608
1215
  background: var(--dsw-alias-bg-layer-2);
1216
+
609
1217
  color: var(--dsw-alias-label-primary);
1218
+
610
1219
  border-radius: 8px;
1220
+
611
1221
  padding: 0 12px;
1222
+
612
1223
  font-size: 13px;
1224
+
613
1225
  width: 100%;
1226
+
614
1227
  box-sizing: border-box;
1228
+
615
1229
  }
1230
+
616
1231
  .dsh-goal-card-input:focus {
1232
+
617
1233
  outline: none;
1234
+
618
1235
  border-color: var(--dsw-alias-state-brand-primary);
1236
+
619
1237
  }
1238
+
620
1239
  .dsh-goal-card-input[aria-invalid="true"] {
1240
+
621
1241
  border-color: var(--dsw-alias-state-error-primary);
1242
+
622
1243
  }
1244
+
623
1245
  .dsh-goal-chev {
1246
+
624
1247
  margin-left: auto;
1248
+
625
1249
  flex: none;
1250
+
626
1251
  color: var(--dsw-alias-label-tertiary);
1252
+
627
1253
  transition: transform .16s ease;
1254
+
628
1255
  }
1256
+
629
1257
  .dsh-goal-chev-open {
1258
+
630
1259
  transform: rotate(180deg);
1260
+
631
1261
  }
1262
+
632
1263
  .dsh-goal-check-row {
1264
+
633
1265
  display: flex;
1266
+
634
1267
  align-items: center;
1268
+
635
1269
  justify-content: space-between;
1270
+
636
1271
  padding: 6px 0;
1272
+
637
1273
  }
1274
+
638
1275
  .dsh-goal-check {
1276
+
639
1277
  display: flex;
1278
+
640
1279
  align-items: center;
1280
+
641
1281
  gap: 8px;
1282
+
642
1283
  font-size: 13px;
1284
+
643
1285
  color: var(--dsw-alias-label-primary);
1286
+
644
1287
  cursor: pointer;
1288
+
645
1289
  }
1290
+
646
1291
  .dsh-goal-foot {
1292
+
647
1293
  border-top: 1px solid var(--dsw-alias-border-l2);
1294
+
648
1295
  display: flex;
1296
+
649
1297
  justify-content: flex-end;
1298
+
650
1299
  align-items: center;
1300
+
651
1301
  gap: 10px;
1302
+
652
1303
  padding: 14px 0 4px;
1304
+
653
1305
  }
1306
+
654
1307
  .dsh-goal-foot-note {
1308
+
655
1309
  color: var(--dsw-alias-state-error-primary);
1310
+
656
1311
  font-size: 12px;
1312
+
657
1313
  margin-right: auto;
1314
+
658
1315
  }
1316
+
659
1317
  .dsh-goal-btn-inline-reset {
1318
+
660
1319
  background: transparent;
1320
+
661
1321
  border: 0;
1322
+
662
1323
  cursor: pointer;
1324
+
663
1325
  padding: 2px 6px;
1326
+
664
1327
  display: flex;
1328
+
665
1329
  align-items: center;
1330
+
666
1331
  gap: 4px;
1332
+
667
1333
  border-radius: 4px;
1334
+
668
1335
  color: var(--dsw-alias-label-tertiary);
1336
+
669
1337
  font-size: 11px;
1338
+
670
1339
  }
1340
+
671
1341
  .dsh-goal-btn-inline-reset:hover {
1342
+
672
1343
  color: var(--dsw-alias-label-primary);
1344
+
673
1345
  background: var(--dsw-alias-bg-layer-2);
1346
+
674
1347
  }
1348
+
675
1349
  .dsh-goal-discard {
1350
+
676
1351
  appearance: none;
1352
+
677
1353
  font: inherit;
1354
+
678
1355
  cursor: pointer;
1356
+
679
1357
  border: 1px solid var(--dsw-alias-border-l2);
1358
+
680
1359
  border-radius: 8px;
1360
+
681
1361
  padding: 6px 14px;
1362
+
682
1363
  font-size: 13px;
1364
+
683
1365
  background: var(--dsw-alias-bg-layer-2);
1366
+
684
1367
  color: var(--dsw-alias-label-primary);
1368
+
685
1369
  transition: all 0.15s ease;
1370
+
686
1371
  }
1372
+
687
1373
  .dsh-goal-discard:hover:not(:disabled) {
1374
+
688
1375
  background: var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-2));
1376
+
689
1377
  border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2));
1378
+
690
1379
  }
1380
+
691
1381
  .dsh-goal-discard:disabled {
1382
+
692
1383
  opacity: 0.4;
1384
+
693
1385
  cursor: not-allowed;
1386
+
694
1387
  }
1388
+
695
1389
  .dsh-goal-save {
1390
+
696
1391
  appearance: none;
1392
+
697
1393
  font: inherit;
1394
+
698
1395
  cursor: pointer;
1396
+
699
1397
  border: 1px solid transparent;
1398
+
700
1399
  border-radius: 8px;
1400
+
701
1401
  padding: 6px 16px;
1402
+
702
1403
  font-size: 13px;
1404
+
703
1405
  background: var(--dsw-alias-label-primary);
1406
+
704
1407
  color: var(--dsw-alias-bg-layer-3);
1408
+
705
1409
  font-weight: 500;
1410
+
706
1411
  transition: opacity 0.15s ease;
1412
+
707
1413
  }
1414
+
708
1415
  .dsh-goal-save:hover:not(:disabled) {
1416
+
709
1417
  opacity: 0.88;
1418
+
710
1419
  }
1420
+
711
1421
  .dsh-goal-save:disabled {
1422
+
712
1423
  opacity: 0.4;
1424
+
713
1425
  cursor: not-allowed;
1426
+
714
1427
  }
1428
+
715
1429
  `;
716
1430
 
1431
+
1432
+
717
1433
  function injectStylesOnce() {
1434
+
718
1435
  if (typeof document === 'undefined') return;
1436
+
719
1437
  if (document.getElementById('dsh-goal-styles')) return;
1438
+
720
1439
  const style = document.createElement('style');
1440
+
721
1441
  style.id = 'dsh-goal-styles';
1442
+
722
1443
  style.dataset.dshPlugin = NS;
1444
+
723
1445
  style.textContent = CSS_STYLES;
1446
+
724
1447
  document.head.appendChild(style);
1448
+
725
1449
  }
726
1450
 
727
1451
 
1452
+
1453
+
1454
+
728
1455
  // Синтез приятных звуковых колокольчиков через Web Audio API (Item 6)
1456
+
729
1457
  function playGoalChime(isSuccess = true) {
1458
+
730
1459
  if (typeof window === 'undefined') return;
1460
+
731
1461
  try {
1462
+
732
1463
  const AudioCtx = window.AudioContext || window.webkitAudioContext;
1464
+
733
1465
  if (!AudioCtx) return;
1466
+
734
1467
  const actx = new AudioCtx();
1468
+
735
1469
  if (actx.state === 'suspended') {
1470
+
736
1471
  actx.resume().catch(() => {});
1472
+
737
1473
  }
1474
+
738
1475
  const now = actx.currentTime;
1476
+
739
1477
  // Пентатонические аккорды: успех — восходящий C5-E5-G5-C6; сбой/ошибка — нисходящий G4-Eb4-C4
1478
+
740
1479
  const notes = isSuccess
1480
+
741
1481
  ? [523.25, 659.25, 783.99, 1046.50]
1482
+
742
1483
  : [392.00, 311.13, 261.63];
743
1484
 
1485
+
1486
+
744
1487
  notes.forEach((freq, idx) => {
1488
+
745
1489
  const osc = actx.createOscillator();
1490
+
746
1491
  const gain = actx.createGain();
1492
+
747
1493
  osc.type = 'sine';
1494
+
748
1495
  osc.frequency.setValueAtTime(freq, now + idx * 0.12);
1496
+
749
1497
 
1498
+
750
1499
  gain.gain.setValueAtTime(0.001, now + idx * 0.12);
1500
+
751
1501
  gain.gain.exponentialRampToValueAtTime(0.15, now + idx * 0.12 + 0.02);
1502
+
752
1503
  gain.gain.exponentialRampToValueAtTime(0.0001, now + idx * 0.12 + 0.4);
753
1504
 
1505
+
1506
+
754
1507
  osc.connect(gain);
1508
+
755
1509
  gain.connect(actx.destination);
756
1510
 
1511
+
1512
+
757
1513
  osc.start(now + idx * 0.12);
1514
+
758
1515
  osc.stop(now + idx * 0.12 + 0.45);
1516
+
759
1517
  });
760
1518
 
1519
+
1520
+
761
1521
  setTimeout(() => {
1522
+
762
1523
  try { actx.close(); } catch (_) {}
1524
+
763
1525
  }, 2000);
1526
+
764
1527
  } catch (_) {}
1528
+
765
1529
  }
766
1530
 
767
1531
 
1532
+
1533
+
1534
+
768
1535
  // --- ГЕНЕРАТОР ОТЧЕТА ДЛЯ GITHUB / GITEA PR COMMENT ---
1536
+
769
1537
  function generateGitHubPRComment(state) {
1538
+
770
1539
  if (!state) return '';
1540
+
771
1541
  const title = state.title || 'Goal Report';
1542
+
772
1543
  const status = state.state === 'COMPLETED' ? 'COMPLETED' : state.state;
1544
+
773
1545
  const elapsed = state.formattedElapsed || '0s';
1546
+
774
1547
  const iter = `${state.iterationsCount || 0}/${state.maxIterations || 25}`;
1548
+
775
1549
  const totalTokens = state.tokensUsage?.totalTokens || 0;
1550
+
776
1551
  const promptTokens = state.tokensUsage?.promptTokens || 0;
1552
+
777
1553
  const compTokens = state.tokensUsage?.completionTokens || 0;
1554
+
778
1555
  const gitCommit = state.gitStartCommit ? `  |  **Git Start:** \`${state.gitStartCommit}\` 📌` : '';
779
1556
 
1557
+
1558
+
780
1559
  let md = `## 🎯 Autonomous Goal Resolution: ${title}\n\n`;
1560
+
781
1561
  md += `> **Status:** \`${status}\` 🚀  |  **Duration:** \`${elapsed}\` ⏱️  |  **Iterations:** \`${iter}\` 🔄${gitCommit}\n\n`;
782
1562
 
1563
+
1564
+
783
1565
  if (totalTokens > 0) {
1566
+
784
1567
  md += `### 📊 Telemetry & Token Usage\n`;
1568
+
785
1569
  md += `- **Total Tokens:** \`${totalTokens.toLocaleString('en-US')}\` (Prompt: \`${promptTokens.toLocaleString('en-US')}\`, Completion: \`${compTokens.toLocaleString('en-US')}\`)\n\n`;
1570
+
786
1571
  }
787
1572
 
1573
+
1574
+
788
1575
  if (state.resultSummary) {
1576
+
789
1577
  md += `### 📦 Deliverables & Achievements\n${state.resultSummary}\n\n`;
1578
+
790
1579
  } else if (state.description) {
1580
+
791
1581
  md += `### 📝 Objective\n${state.description}\n\n`;
1582
+
792
1583
  }
793
1584
 
1585
+
1586
+
794
1587
  const milestones = state.milestones || [];
1588
+
795
1589
  if (milestones.length > 0) {
1590
+
796
1591
  const completedCount = milestones.filter(m => m.status === 'completed').length;
1592
+
797
1593
  md += `<details>\n<summary><b>📋 Milestones Breakdown (${completedCount}/${milestones.length} Completed)</b></summary>\n\n`;
1594
+
798
1595
  md += `| # | Status | Milestone | Notes |\n|---|---|---|---|\n`;
1596
+
799
1597
  milestones.forEach((m, idx) => {
1598
+
800
1599
  const mark = m.status === 'completed' ? '✅ Done' : m.status === 'in_progress' ? '🔄 In Progress' : '⏳ Pending';
1600
+
801
1601
  const cleanTitle = (m.title || '').replace(/\|/g, '\\|');
1602
+
802
1603
  const cleanNotes = (m.notes || '').replace(/\|/g, '\\|');
1604
+
803
1605
  md += `| ${idx + 1} | ${mark} | ${cleanTitle} | ${cleanNotes || '—'} |\n`;
1606
+
804
1607
  });
1608
+
805
1609
  md += `\n</details>\n\n`;
1610
+
806
1611
  }
807
1612
 
1613
+
1614
+
808
1615
  md += `*Automated by [@goodandready/dsh-goal](https://github.com/GooDAnDReaDY/dsh-goal)*\n`;
1616
+
809
1617
  return md;
1618
+
810
1619
  }
811
1620
 
1621
+
1622
+
812
1623
  // --- ГЕНЕРАТОР MARKDOWN-ОТЧЕТА ---
1624
+
813
1625
  function generateMarkdownReport(state) {
1626
+
814
1627
  if (!state) return '';
1628
+
815
1629
  const title = state.title || 'Goal Report';
1630
+
816
1631
  const status = state.state === 'COMPLETED' ? 'COMPLETED' : state.state;
1632
+
817
1633
  const elapsed = state.formattedElapsed || '0s';
1634
+
818
1635
  const iter = `${state.iterationsCount || 0}/${state.maxIterations || 25}`;
1636
+
819
1637
  const totalTokens = state.tokensUsage?.totalTokens || 0;
1638
+
820
1639
  const promptTokens = state.tokensUsage?.promptTokens || 0;
1640
+
821
1641
  const compTokens = state.tokensUsage?.completionTokens || 0;
822
1642
 
1643
+
1644
+
823
1645
  let md = `# 🎯 Goal Report: ${title}\n\n`;
1646
+
824
1647
  md += `**Status:** \`${status}\` | **Duration:** \`${elapsed}\` | **Iterations:** \`${iter}\`\n`;
1648
+
825
1649
  if (totalTokens > 0) {
1650
+
826
1651
  md += `**Tokens:** \`${totalTokens.toLocaleString('en-US')}\` (Prompt: \`${promptTokens.toLocaleString('en-US')}\`, Completion: \`${compTokens.toLocaleString('en-US')}\`)\n`;
1652
+
827
1653
  }
1654
+
828
1655
  md += '\n';
829
1656
 
1657
+
1658
+
830
1659
  if (state.description) {
1660
+
831
1661
  md += `### Description\n${state.description}\n\n`;
1662
+
832
1663
  }
833
1664
 
1665
+
1666
+
834
1667
  if (state.resultSummary) {
1668
+
835
1669
  md += `### Summary & Deliverables\n${state.resultSummary}\n\n`;
1670
+
836
1671
  }
837
1672
 
1673
+
1674
+
838
1675
  const milestones = state.milestones || [];
1676
+
839
1677
  if (milestones.length > 0) {
1678
+
840
1679
  md += `### Milestones\n| # | Status | Title | Notes |\n|---|---|---|---|\n`;
1680
+
841
1681
  milestones.forEach((m, idx) => {
1682
+
842
1683
  const mark = m.status === 'completed' ? '✅ Done' : m.status === 'in_progress' ? '🔄 In Progress' : '⏳ Pending';
1684
+
843
1685
  const cleanTitle = (m.title || '').replace(/\|/g, '\\|');
1686
+
844
1687
  const cleanNotes = (m.notes || '').replace(/\|/g, '\\|');
1688
+
845
1689
  md += `| ${idx + 1} | ${mark} | ${cleanTitle} | ${cleanNotes || '—'} |\n`;
1690
+
846
1691
  });
1692
+
847
1693
  md += '\n';
1694
+
848
1695
  }
849
1696
 
1697
+
1698
+
850
1699
  md += `*Generated by DSH Goal Engine at ${new Date().toISOString()}*\n`;
1700
+
851
1701
  return md;
1702
+
852
1703
  }
853
1704
 
1705
+
1706
+
854
1707
  // --- МОДАЛЬНОЕ ОКНО ДЕТАЛЕЙ (MODAL DETAILS) ---
1708
+
855
1709
  function GoalDetailsModal({ state, onClose, onAction, t }) {
1710
+
856
1711
  if (!state) return null;
857
1712
 
1713
+
1714
+
858
1715
  const milestones = state.milestones || [];
1716
+
859
1717
  const isCompleted = state.state === 'COMPLETED';
1718
+
860
1719
  const [copied, setCopied] = useState(false);
1720
+
861
1721
  const [prCopied, setPrCopied] = useState(false);
1722
+
862
1723
  const [gitCopied, setGitCopied] = useState(false);
1724
+
863
1725
  const [nudgeText, setNudgeText] = useState('');
1726
+
864
1727
  const [nudgeSent, setNudgeSent] = useState(false);
865
1728
 
1729
+
1730
+
866
1731
  const totalTokens = state.tokensUsage?.totalTokens || 0;
1732
+
867
1733
  const promptTokens = state.tokensUsage?.promptTokens || 0;
1734
+
868
1735
  const compTokens = state.tokensUsage?.completionTokens || 0;
1736
+
869
1737
  const tokensFormatted = totalTokens > 0
1738
+
870
1739
  ? (totalTokens >= 1000 ? `${(totalTokens / 1000).toFixed(1)}k` : `${totalTokens}`)
1740
+
871
1741
  : '—';
1742
+
872
1743
  const tokensTooltip = totalTokens > 0
1744
+
873
1745
  ? `Prompt: ${promptTokens.toLocaleString('en-US')} | Completion: ${compTokens.toLocaleString('en-US')} | Total: ${totalTokens.toLocaleString('en-US')}`
874
- : 'Токены ещё не зафиксированы';
1746
+
1747
+ : 'Tokens not recorded yet';
1748
+
1749
+
875
1750
 
876
1751
  const handleCopyReport = () => {
1752
+
877
1753
  const md = generateMarkdownReport(state);
1754
+
878
1755
  if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
1756
+
879
1757
  navigator.clipboard.writeText(md).then(() => {
1758
+
880
1759
  setCopied(true);
1760
+
881
1761
  setTimeout(() => setCopied(false), 2500);
1762
+
882
1763
  }).catch((err) => {
1764
+
883
1765
  console.warn('[dsh-goal] Clipboard copy failed:', err);
1766
+
884
1767
  });
1768
+
885
1769
  }
1770
+
886
1771
  };
887
1772
 
1773
+
1774
+
888
1775
  return React.createElement(
1776
+
889
1777
  'div',
1778
+
890
1779
  { className: 'dsh-goal-modal-overlay', onClick: onClose },
1780
+
891
1781
  React.createElement(
1782
+
892
1783
  'div',
1784
+
893
1785
  {
1786
+
894
1787
  className: 'dsh-goal-modal',
1788
+
895
1789
  onClick: (e) => e.stopPropagation(),
1790
+
896
1791
  },
1792
+
897
1793
  React.createElement(
1794
+
898
1795
  'div',
1796
+
899
1797
  { className: 'dsh-goal-modal-head' },
1798
+
900
1799
  React.createElement(
1800
+
901
1801
  'div',
1802
+
902
1803
  { className: 'dsh-goal-modal-title' },
1804
+
903
1805
  isCompleted
1806
+
904
1807
  ? React.createElement(IconCheck, { size: 18, className: 'cb-badge-ok' })
1808
+
905
1809
  : React.createElement(IconTarget, { size: 18 }),
1810
+
906
1811
  isCompleted ? (t('modalTitleCompleted') || 'Goal completed: plan and results') : (t('modalTitle') || 'Goal plan and status'),
1812
+
907
1813
  ),
1814
+
908
1815
  React.createElement(
1816
+
909
1817
  'button',
1818
+
910
1819
  {
1820
+
911
1821
  className: 'dsh-goal-btn icon-only close',
1822
+
912
1823
  onClick: onClose,
1824
+
913
1825
  title: t('close') || 'Close',
1826
+
914
1827
  },
1828
+
915
1829
  '✕',
1830
+
916
1831
  ),
1832
+
917
1833
  ),
1834
+
918
1835
  React.createElement(
1836
+
919
1837
  'div',
1838
+
920
1839
  { className: 'dsh-goal-modal-body' },
1840
+
921
1841
  React.createElement(
1842
+
922
1843
  'div',
1844
+
923
1845
  null,
1846
+
924
1847
  React.createElement('div', { style: { fontWeight: 600, fontSize: 16, marginBottom: 4, color: 'var(--dsw-alias-label-primary)' } }, state.title),
1848
+
925
1849
  state.description ? React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', fontSize: 13, marginBottom: 6, lineHeight: 1.4 } }, state.description) : null,
1850
+
926
1851
  ),
1852
+
927
1853
  !isCompleted
1854
+
928
1855
  ? React.createElement(
1856
+
929
1857
  'div',
1858
+
930
1859
  { className: 'dsh-goal-nudge-box', style: { marginBottom: 12 } },
1860
+
931
1861
  React.createElement('input', {
1862
+
932
1863
  type: 'text',
1864
+
933
1865
  className: 'dsh-goal-card-input',
1866
+
934
1867
  style: { flex: 1, fontSize: 12 },
935
- placeholder: t('nudgePlaceholder') || '💡 Уточнить задачу или направить агента...',
1868
+
1869
+ placeholder: t('nudgePlaceholder') || '💡 Clarify task or steer agent...',
1870
+
936
1871
  value: nudgeText,
1872
+
937
1873
  onChange: (e) => setNudgeText(e.target.value),
1874
+
938
1875
  onKeyDown: (e) => {
1876
+
939
1877
  if (e.key === 'Enter' && nudgeText.trim()) {
1878
+
940
1879
  onAction('nudge', { text: nudgeText.trim() });
1880
+
941
1881
  setNudgeText('');
1882
+
942
1883
  setNudgeSent(true);
1884
+
943
1885
  setTimeout(() => setNudgeSent(false), 2000);
1886
+
944
1887
  }
1888
+
945
1889
  },
1890
+
946
1891
  }),
1892
+
947
1893
  React.createElement(
1894
+
948
1895
  'button',
1896
+
949
1897
  {
1898
+
950
1899
  type: 'button',
1900
+
951
1901
  className: `dsh-goal-btn ${nudgeSent ? 'dsh-goal-btn-primary' : ''}`,
1902
+
952
1903
  disabled: !nudgeText.trim(),
1904
+
953
1905
  onClick: () => {
1906
+
954
1907
  if (nudgeText.trim()) {
1908
+
955
1909
  onAction('nudge', { text: nudgeText.trim() });
1910
+
956
1911
  setNudgeText('');
1912
+
957
1913
  setNudgeSent(true);
1914
+
958
1915
  setTimeout(() => setNudgeSent(false), 2000);
1916
+
959
1917
  }
1918
+
960
1919
  },
1920
+
961
1921
  },
962
- nudgeSent ? (t('nudgeSent') || '✅ Отправлено!') : (t('sendNudge') || 'Направить'),
1922
+
1923
+ nudgeSent ? (t('nudgeSent') || '✅ Steering sent!') : (t('sendNudge') || 'Steer'),
1924
+
963
1925
  ),
1926
+
964
1927
  )
1928
+
965
1929
  : null,
1930
+
966
1931
  state.gitStartCommit
1932
+
967
1933
  ? React.createElement(
1934
+
968
1935
  'div',
1936
+
969
1937
  { style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 8 } },
1938
+
970
1939
  React.createElement(
1940
+
971
1941
  'span',
1942
+
972
1943
  {
1944
+
973
1945
  className: 'dsh-goal-git-badge',
974
- title: t('copyGitDiff') || 'Кликните, чтобы скопировать команду git diff',
1946
+
1947
+ title: t('copyGitDiff') || 'Click to copy git diff command',
1948
+
975
1949
  onClick: () => {
1950
+
976
1951
  if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
1952
+
977
1953
  navigator.clipboard.writeText(`git diff ${state.gitStartCommit}`).then(() => {
1954
+
978
1955
  setGitCopied(true);
1956
+
979
1957
  setTimeout(() => setGitCopied(false), 2000);
1958
+
980
1959
  });
1960
+
981
1961
  }
1962
+
982
1963
  },
1964
+
983
1965
  },
984
- gitCopied ? '✅ diff скопирован!' : `📌 Git: ${state.gitStartCommit}`,
1966
+
1967
+ gitCopied ? '✅ Diff copied!' : `📌 Git: ${state.gitStartCommit}`,
1968
+
985
1969
  ),
1970
+
986
1971
  )
1972
+
987
1973
  : null,
1974
+
988
1975
  React.createElement(
1976
+
989
1977
  'div',
1978
+
990
1979
  { className: 'dsh-goal-stat-grid' },
1980
+
991
1981
  React.createElement(
1982
+
992
1983
  'div',
1984
+
993
1985
  { className: 'dsh-goal-stat-box' },
1986
+
994
1987
  React.createElement('span', { className: 'dsh-goal-stat-val' }, state.formattedElapsed || '0s'),
1988
+
995
1989
  React.createElement('span', { className: 'dsh-goal-stat-lbl' }, t('time') || 'Running time'),
1990
+
996
1991
  ),
1992
+
997
1993
  React.createElement(
1994
+
998
1995
  'div',
1996
+
999
1997
  { className: 'dsh-goal-stat-box' },
1998
+
1000
1999
  React.createElement('span', { className: 'dsh-goal-stat-val' }, state.formattedETA || (isCompleted ? (t('done') || 'Done') : '—')),
2000
+
1001
2001
  React.createElement('span', { className: 'dsh-goal-stat-lbl' }, t('eta') || 'ETA'),
2002
+
1002
2003
  ),
2004
+
1003
2005
  React.createElement(
2006
+
1004
2007
  'div',
2008
+
1005
2009
  { className: 'dsh-goal-stat-box', title: tokensTooltip },
2010
+
1006
2011
  React.createElement('span', { className: 'dsh-goal-stat-val' }, tokensFormatted),
2012
+
1007
2013
  React.createElement('span', { className: 'dsh-goal-stat-lbl' }, t('tokens') || 'Tokens'),
2014
+
1008
2015
  ),
2016
+
1009
2017
  React.createElement(
2018
+
1010
2019
  'div',
2020
+
1011
2021
  { className: 'dsh-goal-stat-box' },
2022
+
1012
2023
  React.createElement('span', { className: 'dsh-goal-stat-val' }, `${state.iterationsCount || 0}/${state.maxIterations || 25}`),
2024
+
1013
2025
  React.createElement('span', { className: 'dsh-goal-stat-lbl' }, t('iterations') || 'Iterations'),
2026
+
1014
2027
  ),
2028
+
1015
2029
  React.createElement(
2030
+
1016
2031
  'div',
2032
+
1017
2033
  { className: 'dsh-goal-stat-box' },
2034
+
1018
2035
  React.createElement('span', { className: 'dsh-goal-stat-val' }, `${state.progressPercent || 0}%`),
2036
+
1019
2037
  React.createElement('span', { className: 'dsh-goal-stat-lbl' }, t('progress') || 'Progress'),
2038
+
1020
2039
  ),
2040
+
1021
2041
  ),
2042
+
1022
2043
  React.createElement(
2044
+
1023
2045
  'div',
2046
+
1024
2047
  { style: { display: 'flex', flexDirection: 'column', gap: 8, marginTop: 4 } },
2048
+
1025
2049
  React.createElement('div', { style: { fontWeight: 600, fontSize: 13, color: 'var(--dsw-alias-label-primary)' } }, t('milestones') || 'Plan of work:'),
2050
+
1026
2051
  milestones.length === 0
2052
+
1027
2053
  ? React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary)', fontSize: 12, padding: '8px 0' } }, t('noMilestones') || 'Agent is preparing the plan of work...')
2054
+
1028
2055
  : milestones.map((m, i) =>
2056
+
1029
2057
  React.createElement(
2058
+
1030
2059
  'div',
2060
+
1031
2061
  { key: m.id || i, className: 'dsh-goal-milestone-item' },
2062
+
1032
2063
  React.createElement('span', { style: { fontSize: 15 } }, m.status === 'completed' ? '✅' : m.status === 'in_progress' ? '🔄' : '⏳'),
2064
+
1033
2065
  React.createElement(
2066
+
1034
2067
  'div',
2068
+
1035
2069
  { style: { flex: 1, minWidth: 0 } },
2070
+
1036
2071
  React.createElement(
2072
+
1037
2073
  'div',
2074
+
1038
2075
  {
2076
+
1039
2077
  style: {
2078
+
1040
2079
  textDecoration: m.status === 'completed' ? 'line-through' : 'none',
2080
+
1041
2081
  color: m.status === 'completed' ? 'var(--dsw-alias-label-tertiary)' : 'inherit',
2082
+
1042
2083
  fontWeight: m.status === 'in_progress' ? 600 : 400,
2084
+
1043
2085
  },
2086
+
1044
2087
  },
2088
+
1045
2089
  `${i + 1}. ${m.title}`,
2090
+
1046
2091
  ),
2092
+
1047
2093
  m.notes ? React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-label-tertiary)', marginTop: 2 } }, m.notes) : null,
2094
+
1048
2095
  ),
2096
+
1049
2097
  ),
2098
+
1050
2099
  ),
2100
+
1051
2101
  ),
2102
+
1052
2103
  ),
2104
+
1053
2105
  React.createElement(
2106
+
1054
2107
  'div',
2108
+
1055
2109
  { className: 'dsh-goal-modal-foot' },
2110
+
1056
2111
  isCompleted
2112
+
1057
2113
  ? React.createElement(
2114
+
1058
2115
  React.Fragment,
2116
+
1059
2117
  null,
2118
+
1060
2119
  React.createElement(
2120
+
1061
2121
  'button',
2122
+
1062
2123
  {
2124
+
1063
2125
  className: `dsh-goal-btn dsh-goal-btn-copy${copied ? ' copied' : ''}`,
2126
+
1064
2127
  onClick: handleCopyReport,
1065
- title: t('copyReport') || 'Скопировать отчёт в Markdown',
2128
+
2129
+ title: t('copyReport') || 'Copy Report in Markdown',
2130
+
1066
2131
  },
1067
- copied ? (t('reportCopied') || '✅ Скопировано!') : (t('copyReport') || '📋 Скопировать отчёт в Markdown'),
2132
+
2133
+ copied ? (t('reportCopied') || '✅ Copied!') : (t('copyReport') || '📋 Copy Report in Markdown'),
2134
+
1068
2135
  ),
2136
+
1069
2137
  React.createElement(
2138
+
1070
2139
  'button',
2140
+
1071
2141
  {
2142
+
1072
2143
  className: `dsh-goal-btn dsh-goal-btn-copy${prCopied ? ' copied' : ''}`,
2144
+
1073
2145
  onClick: () => {
2146
+
1074
2147
  const prMd = generateGitHubPRComment(state);
2148
+
1075
2149
  if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
2150
+
1076
2151
  navigator.clipboard.writeText(prMd).then(() => {
2152
+
1077
2153
  setPrCopied(true);
2154
+
1078
2155
  setTimeout(() => setPrCopied(false), 2500);
2156
+
1079
2157
  });
2158
+
1080
2159
  }
2160
+
1081
2161
  },
1082
- title: 'Скопировать отчёт для GitHub / Gitea PR Comment',
2162
+
2163
+ title: 'Copy Report for GitHub / Gitea PR Comment',
2164
+
1083
2165
  },
1084
- prCopied ? (t('prCommentCopied') || '✅ PR-отчёт скопирован!') : (t('copyPRComment') || '🐙 Копировать для GitHub PR'),
2166
+
2167
+ prCopied ? (t('prCommentCopied') || '✅ PR Report Copied!') : (t('copyPRComment') || '🐙 Copy for GitHub PR'),
2168
+
1085
2169
  ),
2170
+
1086
2171
  )
2172
+
1087
2173
  : null,
2174
+
1088
2175
  isCompleted
2176
+
1089
2177
  ? React.createElement(
2178
+
1090
2179
  'button',
2180
+
1091
2181
  {
2182
+
1092
2183
  className: 'dsh-goal-btn dsh-goal-btn-danger',
2184
+
1093
2185
  onClick: () => {
2186
+
1094
2187
  onAction('clear');
2188
+
1095
2189
  onClose();
2190
+
1096
2191
  },
2192
+
1097
2193
  },
1098
- t('closeBanner') || 'Закрыть плашку цели',
2194
+
2195
+ t('closeBanner') || 'Close goal banner',
2196
+
1099
2197
  )
2198
+
1100
2199
  : null,
2200
+
1101
2201
  React.createElement(
2202
+
1102
2203
  'button',
2204
+
1103
2205
  {
2206
+
1104
2207
  className: 'dsh-goal-btn dsh-goal-btn-primary',
2208
+
1105
2209
  onClick: onClose,
2210
+
1106
2211
  },
1107
- t('close') || 'Закрыть',
2212
+
2213
+ t('close') || 'Close',
2214
+
1108
2215
  ),
2216
+
1109
2217
  ),
2218
+
1110
2219
  ),
2220
+
1111
2221
  );
2222
+
1112
2223
  }
1113
2224
 
2225
+
2226
+
1114
2227
  // --- МОДАЛЬНОЕ ОКНО БЫСТРОГО ЗАПУСКА ЦЕЛИ (QUICK LAUNCH MODAL) ---
2228
+
1115
2229
  function QuickLaunchModal({ onClose, onStart, t }) {
2230
+
1116
2231
  const [title, setTitle] = useState('');
2232
+
1117
2233
  const inputRef = useRef(null);
1118
2234
 
2235
+
2236
+
1119
2237
  useEffect(() => {
2238
+
1120
2239
  if (inputRef.current) inputRef.current.focus();
2240
+
1121
2241
  }, []);
1122
2242
 
2243
+
2244
+
1123
2245
  const handleSubmit = (e) => {
2246
+
1124
2247
  if (e) e.preventDefault();
2248
+
1125
2249
  const trimmed = title.trim();
2250
+
1126
2251
  if (trimmed) {
2252
+
1127
2253
  onStart(trimmed);
2254
+
1128
2255
  }
2256
+
1129
2257
  };
1130
2258
 
2259
+
2260
+
1131
2261
  return React.createElement(
2262
+
1132
2263
  'div',
2264
+
1133
2265
  { className: 'dsh-goal-modal-overlay', onClick: onClose },
2266
+
1134
2267
  React.createElement(
2268
+
1135
2269
  'div',
2270
+
1136
2271
  {
2272
+
1137
2273
  className: 'dsh-goal-modal',
2274
+
1138
2275
  style: { maxWidth: 480 },
2276
+
1139
2277
  onClick: (e) => e.stopPropagation(),
2278
+
1140
2279
  },
2280
+
1141
2281
  React.createElement(
2282
+
1142
2283
  'div',
2284
+
1143
2285
  { className: 'dsh-goal-modal-head' },
2286
+
1144
2287
  React.createElement(
2288
+
1145
2289
  'div',
2290
+
1146
2291
  { className: 'dsh-goal-modal-title' },
2292
+
1147
2293
  React.createElement(IconTarget, { size: 18 }),
1148
- t('quickLaunchTitle') || 'Быстрый запуск цели',
2294
+
2295
+ t('quickLaunchTitle') || 'Quick Launch Goal',
2296
+
1149
2297
  ),
2298
+
1150
2299
  React.createElement(
2300
+
1151
2301
  'button',
1152
- { className: 'dsh-goal-btn icon-only close', onClick: onClose, title: t('close') || 'Закрыть' },
2302
+
2303
+ { className: 'dsh-goal-btn icon-only close', onClick: onClose, title: t('close') || 'Close' },
2304
+
1153
2305
  '✕',
2306
+
1154
2307
  ),
2308
+
1155
2309
  ),
2310
+
1156
2311
  React.createElement(
2312
+
1157
2313
  'form',
2314
+
1158
2315
  { onSubmit: handleSubmit },
2316
+
1159
2317
  React.createElement(
2318
+
1160
2319
  'div',
2320
+
1161
2321
  { className: 'dsh-goal-modal-body' },
2322
+
1162
2323
  React.createElement(
2324
+
1163
2325
  'div',
2326
+
1164
2327
  { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)', marginBottom: 8 } },
1165
- t('quickLaunchDesc') || 'Сформулируйте задачу для агента в автономном режиме:',
2328
+
2329
+ t('quickLaunchDesc') || 'Define the objective for the agent in autonomous mode:',
2330
+
1166
2331
  ),
2332
+
1167
2333
  React.createElement(
2334
+
1168
2335
  'div',
2336
+
1169
2337
  { className: 'dsh-goal-template-chips' },
2338
+
1170
2339
  QUICK_LAUNCH_TEMPLATES.map((tmpl) => {
1171
- const lang = t('localeCode') || 'ru';
2340
+
2341
+ const lang = t('localeCode') || 'en';
2342
+
1172
2343
  const label = tmpl.label[lang] || tmpl.label.en;
2344
+
1173
2345
  const prefix = tmpl.prefix[lang] || tmpl.prefix.en;
2346
+
1174
2347
  return React.createElement(
2348
+
1175
2349
  'button',
2350
+
1176
2351
  {
2352
+
1177
2353
  key: tmpl.id,
2354
+
1178
2355
  type: 'button',
2356
+
1179
2357
  className: 'dsh-goal-template-chip',
2358
+
1180
2359
  title: prefix,
2360
+
1181
2361
  onClick: () => {
2362
+
1182
2363
  setTitle(prefix);
2364
+
1183
2365
  if (inputRef.current) inputRef.current.focus();
2366
+
1184
2367
  },
2368
+
1185
2369
  },
2370
+
1186
2371
  `${tmpl.icon} ${label}`,
2372
+
1187
2373
  );
2374
+
1188
2375
  }),
2376
+
1189
2377
  ),
2378
+
1190
2379
  React.createElement('input', {
2380
+
1191
2381
  ref: inputRef,
2382
+
1192
2383
  type: 'text',
2384
+
1193
2385
  className: 'dsh-goal-card-input',
2386
+
1194
2387
  style: { width: '100%', boxSizing: 'border-box', fontSize: 13, padding: '8px 10px' },
1195
- placeholder: t('quickLaunchPlaceholder') || 'Например: Реализовать экспорт отчетов и покрыть тестами...',
2388
+
2389
+ placeholder: t('quickLaunchPlaceholder') || 'e.g. Implement report export and cover with unit tests...',
2390
+
1196
2391
  value: title,
2392
+
1197
2393
  onChange: (e) => setTitle(e.target.value),
2394
+
1198
2395
  onKeyDown: (e) => {
2396
+
1199
2397
  if (e.key === 'Enter' && !e.shiftKey) {
2398
+
1200
2399
  e.preventDefault();
2400
+
1201
2401
  handleSubmit();
2402
+
1202
2403
  }
2404
+
1203
2405
  },
2406
+
1204
2407
  }),
2408
+
1205
2409
  ),
2410
+
1206
2411
  React.createElement(
2412
+
1207
2413
  'div',
2414
+
1208
2415
  { className: 'dsh-goal-modal-foot' },
2416
+
1209
2417
  React.createElement(
2418
+
1210
2419
  'button',
2420
+
1211
2421
  { type: 'button', className: 'dsh-goal-btn', onClick: onClose },
1212
- t('close') || 'Отмена',
2422
+
2423
+ t('close') || 'Cancel',
2424
+
1213
2425
  ),
2426
+
1214
2427
  React.createElement(
2428
+
1215
2429
  'button',
2430
+
1216
2431
  {
2432
+
1217
2433
  type: 'submit',
2434
+
1218
2435
  className: 'dsh-goal-btn dsh-goal-btn-primary',
2436
+
1219
2437
  disabled: !title.trim(),
2438
+
1220
2439
  },
1221
- t('startGoal') || 'Запустить цель',
2440
+
2441
+ t('startGoal') || 'Start Goal',
2442
+
1222
2443
  ),
2444
+
1223
2445
  ),
2446
+
1224
2447
  ),
2448
+
1225
2449
  ),
2450
+
1226
2451
  );
2452
+
1227
2453
  }
1228
2454
 
2455
+
2456
+
1229
2457
  function sessionIdOf(ctx, props) {
2458
+
1230
2459
  try {
2460
+
1231
2461
  if (props?.session?.id) return String(props.session.id);
2462
+
1232
2463
  if (props?.session?.sessionId) return String(props.session.sessionId);
2464
+
1233
2465
  if (props?.input?.sessionId) return String(props.input.sessionId);
2466
+
1234
2467
  if (ctx?.scope?.session) return String(ctx.scope.session.id || ctx.scope.session.header?.id || '');
2468
+
1235
2469
  if (typeof ctx?.scope?.id === 'string') return ctx.scope.id;
2470
+
1236
2471
  if (ctx?.session) return String(ctx.session.id || ctx.session.header?.id || '');
2472
+
1237
2473
  if (typeof window !== 'undefined' && window.location) {
2474
+
1238
2475
  const params = new URLSearchParams(window.location.search);
2476
+
1239
2477
  const fromUrl = params.get('session') || params.get('sessionId');
2478
+
1240
2479
  if (fromUrl) return fromUrl;
2480
+
1241
2481
  }
2482
+
1242
2483
  } catch (_) {}
2484
+
1243
2485
  return 'default';
2486
+
1244
2487
  }
1245
2488
 
2489
+
2490
+
1246
2491
  // --- ПАНЕЛЬ ЦЕЛИ НАД ПОЛЕМ ВВОДА (COMPOSER DOCK WIDGET) ---
2492
+
1247
2493
  function GoalTopBanner(props) {
2494
+
1248
2495
  const { ctx } = props || {};
2496
+
1249
2497
  const sid = sessionIdOf(ctx, props);
2498
+
1250
2499
  const [state, setState] = useState(null);
2500
+
1251
2501
  const [isModalOpen, setIsModalOpen] = useState(false);
2502
+
1252
2503
  const [isQuickLaunchOpen, setIsQuickLaunchOpen] = useState(false);
2504
+
1253
2505
  const [now, setNow] = useState(() => Date.now());
2506
+
1254
2507
  const stateRef = useRef(null);
2508
+
1255
2509
  stateRef.current = state;
2510
+
1256
2511
  const lastStateRef = useRef(null);
1257
2512
 
2513
+
2514
+
1258
2515
  const resolveLocale = () => {
2516
+
1259
2517
  let active = 'en';
2518
+
1260
2519
  if (ctx?.locale?.getSnapshot) {
2520
+
1261
2521
  const snapLoc = ctx.locale.getSnapshot();
2522
+
1262
2523
  if (snapLoc?.active) {
2524
+
1263
2525
  const code = snapLoc.active.toLowerCase();
2526
+
1264
2527
  if (code.startsWith('zh')) active = 'zh';
2528
+
1265
2529
  else if (code.startsWith('ru')) active = 'ru';
2530
+
1266
2531
  else active = 'en';
2532
+
1267
2533
  }
2534
+
1268
2535
  } else if (typeof navigator !== 'undefined' && navigator.language) {
2536
+
1269
2537
  const code = navigator.language.toLowerCase();
2538
+
1270
2539
  if (code.startsWith('zh')) active = 'zh';
2540
+
1271
2541
  else if (code.startsWith('ru')) active = 'ru';
2542
+
1272
2543
  else active = 'en';
2544
+
1273
2545
  }
2546
+
1274
2547
  if (state?.lang) {
2548
+
1275
2549
  if (state.lang === 'zh') active = 'zh';
2550
+
1276
2551
  else if (state.lang === 'ru') active = 'ru';
2552
+
1277
2553
  else if (state.lang === 'en') active = 'en';
2554
+
1278
2555
  }
2556
+
1279
2557
  return active;
2558
+
1280
2559
  };
1281
2560
 
1282
- const t = (key) => {
2561
+
2562
+
2563
+ const t = (key) => {
2564
+
1283
2565
  const active = resolveLocale();
2566
+
1284
2567
  return LOCALES[active]?.[key] || LOCALES.en?.[key] || LOCALES.ru?.[key] || key;
2568
+
1285
2569
  };
1286
2570
 
2571
+
2572
+
1287
2573
  useEffect(() => {
2574
+
1288
2575
  injectStylesOnce();
2576
+
1289
2577
  }, []);
1290
2578
 
2579
+
2580
+
1291
2581
  // Локальный секундный интервал для плавного и бесперебойного тикания таймера
2582
+
1292
2583
  useEffect(() => {
2584
+
1293
2585
  const ticker = setInterval(() => {
2586
+
1294
2587
  setNow(Date.now());
2588
+
1295
2589
  }, 1000);
2590
+
1296
2591
  return () => clearInterval(ticker);
2592
+
1297
2593
  }, []);
1298
2594
 
2595
+
2596
+
1299
2597
  const fetchState = useCallback(async () => {
2598
+
1300
2599
  try {
2600
+
1301
2601
  const query = sid && sid !== 'default' ? `?sessionId=${encodeURIComponent(sid)}` : '';
2602
+
1302
2603
  const res = await fetch(`/dsh-goal/state${query}`, {
2604
+
1303
2605
  headers: { 'x-dsh-session-id': sid },
2606
+
1304
2607
  });
2608
+
1305
2609
  if (res.ok) {
2610
+
1306
2611
  const data = await res.json();
2612
+
1307
2613
  updateStateWithAudio(data);
2614
+
1308
2615
  return data;
2616
+
1309
2617
  }
2618
+
1310
2619
  } catch (_) {}
2620
+
1311
2621
  }, [sid]);
1312
2622
 
2623
+
2624
+
1313
2625
  // Звуковое оповещение при переходе в COMPLETED или FAILED
2626
+
1314
2627
  const updateStateWithAudio = useCallback((nextState) => {
2628
+
1315
2629
  if (nextState && lastStateRef.current) {
2630
+
1316
2631
  const prev = lastStateRef.current;
2632
+
1317
2633
  if (prev.state !== nextState.state && nextState.enableSound !== false) {
2634
+
1318
2635
  if (nextState.state === 'COMPLETED') {
2636
+
1319
2637
  playGoalChime(true);
2638
+
1320
2639
  } else if (nextState.state === 'FAILED') {
2640
+
1321
2641
  playGoalChime(false);
2642
+
1322
2643
  }
1323
2644
 
2645
+
2646
+
1324
2647
  // HTML5 Browser Desktop Notification
2648
+
1325
2649
  if (typeof window !== 'undefined' && 'Notification' in window) {
2650
+
1326
2651
  if (nextState.enableBrowserNotifications !== false && Notification.permission === 'granted') {
2652
+
1327
2653
  try {
2654
+
1328
2655
  const isOk = nextState.state === 'COMPLETED';
2656
+
1329
2657
  new Notification(
1330
- isOk ? '🎯 Цель выполнена' : '⚠️ Ошибка цели',
2658
+
2659
+ isOk ? '🎯 Goal Completed' : '⚠️ Goal Failed',
2660
+
1331
2661
  {
2662
+
1332
2663
  body: `${nextState.title} (${nextState.formattedElapsed || ''})`,
2664
+
1333
2665
  icon: '/favicon.ico',
2666
+
1334
2667
  }
2668
+
1335
2669
  );
2670
+
1336
2671
  } catch (_) {}
2672
+
1337
2673
  }
2674
+
1338
2675
  }
2676
+
1339
2677
  }
2678
+
1340
2679
  }
2680
+
1341
2681
  lastStateRef.current = nextState;
2682
+
1342
2683
  setState(nextState);
2684
+
1343
2685
  }, []);
1344
2686
 
2687
+
2688
+
1345
2689
  // Realtime Server-Sent Events (SSE) с автоматическим адаптивным fallback-поллингом
2690
+
1346
2691
  useEffect(() => {
2692
+
1347
2693
  let es = null;
2694
+
1348
2695
  let timer = null;
2696
+
1349
2697
  let reconnectTimer = null;
2698
+
1350
2699
  let isDisposed = false;
2700
+
1351
2701
  let sseActive = false;
2702
+
1352
2703
  let retryCount = 0;
1353
2704
 
2705
+
2706
+
1354
2707
  const scheduleNextPoll = () => {
2708
+
1355
2709
  if (isDisposed || sseActive) return;
2710
+
1356
2711
  const currentState = stateRef.current;
2712
+
1357
2713
  const isDocHidden = typeof document !== 'undefined' && document.hidden;
2714
+
1358
2715
  const isRunning = currentState?.hasActiveGoal && currentState?.state === 'RUNNING';
1359
2716
 
2717
+
2718
+
1360
2719
  let delay = 1500;
2720
+
1361
2721
  if (isDocHidden) {
2722
+
1362
2723
  delay = 10000;
2724
+
1363
2725
  } else if (!isRunning) {
2726
+
1364
2727
  delay = 8000;
2728
+
1365
2729
  }
1366
2730
 
2731
+
2732
+
1367
2733
  timer = setTimeout(async () => {
2734
+
1368
2735
  await fetchState();
2736
+
1369
2737
  scheduleNextPoll();
2738
+
1370
2739
  }, delay);
2740
+
1371
2741
  };
1372
2742
 
2743
+
2744
+
1373
2745
  const connectSSE = () => {
2746
+
1374
2747
  if (isDisposed) return;
2748
+
1375
2749
  if (typeof window === 'undefined' || typeof window.EventSource === 'undefined') {
2750
+
1376
2751
  scheduleNextPoll();
2752
+
1377
2753
  return;
2754
+
1378
2755
  }
1379
2756
 
2757
+
2758
+
1380
2759
  try {
2760
+
1381
2761
  if (es) {
2762
+
1382
2763
  es.close();
2764
+
1383
2765
  es = null;
2766
+
1384
2767
  }
1385
2768
 
2769
+
2770
+
1386
2771
  const query = sid && sid !== 'default' ? `?sessionId=${encodeURIComponent(sid)}` : '';
2772
+
1387
2773
  es = new window.EventSource(`/dsh-goal/events${query}`);
1388
2774
 
2775
+
2776
+
1389
2777
  es.onopen = () => {
2778
+
1390
2779
  sseActive = true;
2780
+
1391
2781
  retryCount = 0;
2782
+
1392
2783
  if (timer) {
2784
+
1393
2785
  clearTimeout(timer);
2786
+
1394
2787
  timer = null;
2788
+
1395
2789
  }
2790
+
1396
2791
  if (reconnectTimer) {
2792
+
1397
2793
  clearTimeout(reconnectTimer);
2794
+
1398
2795
  reconnectTimer = null;
2796
+
1399
2797
  }
2798
+
1400
2799
  };
1401
2800
 
2801
+
2802
+
1402
2803
  es.onmessage = (event) => {
2804
+
1403
2805
  if (isDisposed) return;
2806
+
1404
2807
  try {
2808
+
1405
2809
  const data = JSON.parse(event.data);
2810
+
1406
2811
  if (data && typeof data === 'object') {
2812
+
1407
2813
  updateStateWithAudio(data);
2814
+
1408
2815
  }
2816
+
1409
2817
  } catch (_) {}
2818
+
1410
2819
  };
1411
2820
 
2821
+
2822
+
1412
2823
  es.onerror = () => {
2824
+
1413
2825
  sseActive = false;
2826
+
1414
2827
  if (es) {
2828
+
1415
2829
  es.close();
2830
+
1416
2831
  es = null;
2832
+
1417
2833
  }
2834
+
1418
2835
  if (!isDisposed) {
2836
+
1419
2837
  // Запускаем немедленный опрос для непрерывности работы UI
2838
+
1420
2839
  if (!timer) scheduleNextPoll();
1421
2840
 
2841
+
2842
+
1422
2843
  // Экспоненциальный backoff для повторного подключения SSE (от 2s до 30s) с джиттером
2844
+
1423
2845
  retryCount = Math.min(retryCount + 1, 5);
2846
+
1424
2847
  const backoffBase = Math.min(30000, 2000 * Math.pow(1.8, retryCount - 1));
2848
+
1425
2849
  const jitter = Math.random() * 1000;
2850
+
1426
2851
  const reconnectDelay = Math.round(backoffBase + jitter);
1427
2852
 
2853
+
2854
+
1428
2855
  if (reconnectTimer) clearTimeout(reconnectTimer);
2856
+
1429
2857
  reconnectTimer = setTimeout(() => {
2858
+
1430
2859
  reconnectTimer = null;
2860
+
1431
2861
  connectSSE();
2862
+
1432
2863
  }, reconnectDelay);
2864
+
1433
2865
  }
2866
+
1434
2867
  };
2868
+
1435
2869
  } catch (_) {
2870
+
1436
2871
  sseActive = false;
2872
+
1437
2873
  scheduleNextPoll();
2874
+
1438
2875
  }
2876
+
1439
2877
  };
1440
2878
 
2879
+
2880
+
1441
2881
  connectSSE();
1442
2882
 
2883
+
2884
+
1443
2885
  // Первоначальная загрузка состояния
2886
+
1444
2887
  fetchState().then(() => {
2888
+
1445
2889
  if (!sseActive) scheduleNextPoll();
2890
+
1446
2891
  });
1447
2892
 
2893
+
2894
+
1448
2895
  const onVisibilityChange = () => {
2896
+
1449
2897
  if (typeof document !== 'undefined' && !document.hidden) {
2898
+
1450
2899
  if (!sseActive) {
2900
+
1451
2901
  if (timer) clearTimeout(timer);
2902
+
1452
2903
  fetchState().then(() => {
2904
+
1453
2905
  scheduleNextPoll();
2906
+
1454
2907
  });
2908
+
1455
2909
  // При возвращении на вкладку можно попытаться сразу переподключить SSE
2910
+
1456
2911
  if (!reconnectTimer && !sseActive) {
2912
+
1457
2913
  connectSSE();
2914
+
1458
2915
  }
2916
+
1459
2917
  }
2918
+
1460
2919
  }
2920
+
1461
2921
  };
1462
2922
 
2923
+
2924
+
1463
2925
  if (typeof document !== 'undefined' && document.addEventListener) {
2926
+
1464
2927
  document.addEventListener('visibilitychange', onVisibilityChange);
2928
+
1465
2929
  }
1466
2930
 
2931
+
2932
+
1467
2933
  return () => {
2934
+
1468
2935
  isDisposed = true;
2936
+
1469
2937
  if (es) {
2938
+
1470
2939
  es.close();
2940
+
1471
2941
  es = null;
2942
+
1472
2943
  }
2944
+
1473
2945
  if (timer) clearTimeout(timer);
2946
+
1474
2947
  if (reconnectTimer) clearTimeout(reconnectTimer);
2948
+
1475
2949
  if (typeof document !== 'undefined' && document.removeEventListener) {
2950
+
1476
2951
  document.removeEventListener('visibilitychange', onVisibilityChange);
2952
+
1477
2953
  }
2954
+
1478
2955
  };
2956
+
1479
2957
  }, [sid, fetchState, updateStateWithAudio]);
1480
2958
 
2959
+
2960
+
1481
2961
  const handleAction = async (action, extra = {}) => {
2962
+
1482
2963
  try {
2964
+
1483
2965
  const res = await fetch('/dsh-goal/action', {
2966
+
1484
2967
  method: 'POST',
2968
+
1485
2969
  headers: {
2970
+
1486
2971
  'Content-Type': 'application/json',
2972
+
1487
2973
  'x-dsh-session-id': sid,
2974
+
1488
2975
  },
2976
+
1489
2977
  body: JSON.stringify({ action, sessionId: sid, ...extra }),
2978
+
1490
2979
  });
2980
+
1491
2981
  if (res.ok) {
2982
+
1492
2983
  const data = await res.json();
2984
+
1493
2985
  if (data.state) updateStateWithAudio(data.state);
2986
+
1494
2987
  }
2988
+
1495
2989
  } catch (_) {}
2990
+
1496
2991
  };
1497
2992
 
2993
+
2994
+
1498
2995
  const showQuickLaunch = state ? (state.showQuickLaunchButton !== false) : true;
1499
2996
 
2997
+
2998
+
1500
2999
  if (!state || !state.hasActiveGoal) {
3000
+
1501
3001
  if (!showQuickLaunch) return null;
3002
+
1502
3003
  return React.createElement(
3004
+
1503
3005
  React.Fragment,
3006
+
1504
3007
  null,
3008
+
1505
3009
  React.createElement(
3010
+
1506
3011
  'div',
3012
+
1507
3013
  { className: 'dsh-goal-dock dsh-goal-dock-quick', 'data-goal-dock': 'true' },
3014
+
1508
3015
  React.createElement(
3016
+
1509
3017
  'button',
3018
+
1510
3019
  {
3020
+
1511
3021
  className: 'dsh-goal-quicklaunch-btn',
1512
- title: t('quickLaunch') || 'Запустить цель (Goal Mode)',
3022
+
3023
+ title: t('quickLaunch') || 'Start Goal (Goal Mode)',
3024
+
1513
3025
  onClick: () => setIsQuickLaunchOpen(true),
3026
+
1514
3027
  },
3028
+
1515
3029
  React.createElement(IconTarget, { size: 14 }),
1516
- React.createElement('span', null, t('quickLaunch') || 'Запустить цель'),
3030
+
3031
+ React.createElement('span', null, t('quickLaunch') || 'Start Goal'),
3032
+
1517
3033
  ),
3034
+
1518
3035
  ),
3036
+
1519
3037
  isQuickLaunchOpen
3038
+
1520
3039
  ? React.createElement(QuickLaunchModal, {
3040
+
1521
3041
  onClose: () => setIsQuickLaunchOpen(false),
3042
+
1522
3043
  onStart: async (title) => {
3044
+
1523
3045
  await handleAction('start', { title });
3046
+
1524
3047
  setIsQuickLaunchOpen(false);
3048
+
1525
3049
  },
3050
+
1526
3051
  t,
3052
+
1527
3053
  })
3054
+
1528
3055
  : null,
3056
+
1529
3057
  );
3058
+
1530
3059
  }
1531
3060
 
3061
+
3062
+
1532
3063
  const isPaused = state.state === 'PAUSED';
3064
+
1533
3065
  const isCompleted = state.state === 'COMPLETED';
1534
3066
 
3067
+
3068
+
1535
3069
  const formatLiveElapsed = () => {
3070
+
1536
3071
  if (!state) return '0s';
3072
+
1537
3073
  if (!state.startedAt) return state.formattedElapsed || '0s';
3074
+
1538
3075
  const endTime = state.completedAt || (state.pausedAt || now);
3076
+
1539
3077
  const elapsedSec = Math.max(0, Math.floor((endTime - state.startedAt - (state.totalPausedDurationMs || 0)) / 1000));
3078
+
1540
3079
  return formatElapsed(elapsedSec);
3080
+
1541
3081
  };
1542
3082
 
3083
+
3084
+
1543
3085
  const liveElapsedStr = formatLiveElapsed();
3086
+
1544
3087
  const etaStr = state?.formattedETA ? ` (ETA ${state.formattedETA})` : '';
1545
3088
 
3089
+
3090
+
1546
3091
  return React.createElement(
3092
+
1547
3093
  React.Fragment,
3094
+
1548
3095
  null,
3096
+
1549
3097
  React.createElement(
3098
+
1550
3099
  'div',
3100
+
1551
3101
  { className: 'dsh-goal-dock', 'data-goal-dock': 'true' },
3102
+
1552
3103
  React.createElement(
3104
+
1553
3105
  'div',
3106
+
1554
3107
  { className: `dsh-goal-banner${isCompleted ? ' completed' : ''}` },
3108
+
1555
3109
  React.createElement(
3110
+
1556
3111
  'div',
3112
+
1557
3113
  { className: 'dsh-goal-left' },
3114
+
1558
3115
  React.createElement(
3116
+
1559
3117
  'span',
3118
+
1560
3119
  { className: 'dsh-goal-icon' },
3120
+
1561
3121
  isCompleted ? React.createElement(IconCheck, { size: 16 }) : React.createElement(IconTarget, { size: 15 }),
3122
+
1562
3123
  ),
3124
+
1563
3125
  React.createElement(
3126
+
1564
3127
  'span',
3128
+
1565
3129
  {
3130
+
1566
3131
  className: `dsh-goal-badge ${isCompleted ? 'dsh-goal-badge-ok' : isPaused ? 'dsh-goal-badge-warn' : ''}`,
3132
+
1567
3133
  },
3134
+
1568
3135
  isCompleted ? (t('goalCompleted') || 'Goal completed') : isPaused ? (t('paused') || 'Paused') : (t('goalLabel') || 'Current goal'),
3136
+
1569
3137
  ),
3138
+
1570
3139
  React.createElement('span', { className: 'dsh-goal-title', title: state.title }, state.title),
3140
+
1571
3141
  ),
3142
+
1572
3143
  React.createElement(
3144
+
1573
3145
  'div',
3146
+
1574
3147
  { className: 'dsh-goal-right' },
3148
+
1575
3149
  React.createElement(
3150
+
1576
3151
  'span',
3152
+
1577
3153
  { className: 'dsh-goal-time' },
3154
+
1578
3155
  `⏱ ${liveElapsedStr}${etaStr}`,
3156
+
1579
3157
  ),
3158
+
1580
3159
  !isCompleted
3160
+
1581
3161
  ? React.createElement(
3162
+
1582
3163
  'button',
3164
+
1583
3165
  {
3166
+
1584
3167
  className: 'dsh-goal-btn icon-only',
3168
+
1585
3169
  title: isPaused ? (t('resume') || 'Resume') : (t('pause') || 'Pause'),
3170
+
1586
3171
  onClick: () => handleAction(isPaused ? 'resume' : 'pause'),
3172
+
1587
3173
  },
3174
+
1588
3175
  isPaused ? React.createElement(IconPlay, { size: 14 }) : React.createElement(IconPause, { size: 14 }),
3176
+
1589
3177
  )
3178
+
1590
3179
  : null,
3180
+
1591
3181
  React.createElement(
3182
+
1592
3183
  'button',
3184
+
1593
3185
  {
3186
+
1594
3187
  className: 'dsh-goal-btn icon-only',
3188
+
1595
3189
  title: t('details') || 'Expand goal details',
3190
+
1596
3191
  onClick: () => setIsModalOpen(true),
3192
+
1597
3193
  },
3194
+
1598
3195
  React.createElement(IconExpand, { size: 14 }),
3196
+
1599
3197
  ),
3198
+
1600
3199
  isCompleted
3200
+
1601
3201
  ? React.createElement(
3202
+
1602
3203
  'button',
3204
+
1603
3205
  {
3206
+
1604
3207
  className: 'dsh-goal-btn icon-only close',
3208
+
1605
3209
  title: t('closeBanner') || 'Close goal banner',
3210
+
1606
3211
  onClick: () => handleAction('clear'),
3212
+
1607
3213
  },
3214
+
1608
3215
  '✕',
3216
+
1609
3217
  )
3218
+
1610
3219
  : null,
3220
+
1611
3221
  ),
3222
+
1612
3223
  ),
3224
+
1613
3225
  ),
3226
+
1614
3227
  isModalOpen
3228
+
1615
3229
  ? React.createElement(GoalDetailsModal, {
3230
+
1616
3231
  state,
3232
+
1617
3233
  onClose: () => setIsModalOpen(false),
3234
+
1618
3235
  onAction: handleAction,
3236
+
1619
3237
  t,
3238
+
1620
3239
  })
3240
+
1621
3241
  : null,
3242
+
1622
3243
  );
3244
+
1623
3245
  }
1624
3246
 
3247
+
3248
+
1625
3249
  // --- ЛОГИКА ФОРМЫ НАСТРОЕК ---
3250
+
1626
3251
  const DEFAULT_SETTINGS = {
3252
+
1627
3253
  maxIterations: 25,
3254
+
1628
3255
  autoDrive: true,
3256
+
1629
3257
  enableSound: true,
3258
+
1630
3259
  showQuickLaunchButton: true,
3260
+
1631
3261
  consecutiveToolFailureLimit: 3,
3262
+
1632
3263
  enableBrowserNotifications: true,
3264
+
1633
3265
  };
1634
3266
 
3267
+
3268
+
1635
3269
  function parseNumberField(raw) {
3270
+
1636
3271
  if (raw === '' || raw === null || raw === undefined) {
3272
+
1637
3273
  return undefined;
3274
+
1638
3275
  }
3276
+
1639
3277
  const n = Number(raw);
3278
+
1640
3279
  if (!Number.isFinite(n) || n < 1 || !Number.isInteger(n)) {
3280
+
1641
3281
  return NaN;
3282
+
1642
3283
  }
3284
+
1643
3285
  return n;
3286
+
1644
3287
  }
1645
3288
 
3289
+
3290
+
1646
3291
  function getFieldStatus(field, draftValue, snap) {
3292
+
1647
3293
  const baseVal = snap?.base?.[field] ?? DEFAULT_SETTINGS[field];
3294
+
1648
3295
  const userVal = snap?.user?.[field];
3296
+
1649
3297
  const effectiveVal = snap?.value?.[field] ?? baseVal;
1650
3298
 
3299
+
3300
+
1651
3301
  const isOverridden = userVal !== undefined && userVal !== null;
3302
+
1652
3303
  const currentVal = draftValue !== undefined ? draftValue : effectiveVal;
1653
3304
 
3305
+
3306
+
1654
3307
  let invalid = false;
3308
+
1655
3309
  let isDirty = false;
1656
3310
 
3311
+
3312
+
1657
3313
  if (field === 'maxIterations') {
3314
+
1658
3315
  if (draftValue !== undefined) {
3316
+
1659
3317
  if (draftValue === '') {
3318
+
1660
3319
  invalid = false;
3320
+
1661
3321
  isDirty = userVal !== undefined;
3322
+
1662
3323
  } else {
3324
+
1663
3325
  const parsed = parseNumberField(draftValue);
3326
+
1664
3327
  if (Number.isNaN(parsed)) {
3328
+
1665
3329
  invalid = true;
3330
+
1666
3331
  isDirty = true;
3332
+
1667
3333
  } else {
3334
+
1668
3335
  isDirty = parsed !== (userVal !== undefined ? userVal : baseVal);
3336
+
1669
3337
  }
3338
+
1670
3339
  }
3340
+
1671
3341
  }
3342
+
1672
3343
  } else {
3344
+
1673
3345
  if (draftValue !== undefined) {
3346
+
1674
3347
  isDirty = Boolean(draftValue) !== (userVal !== undefined ? Boolean(userVal) : Boolean(baseVal));
3348
+
1675
3349
  }
3350
+
1676
3351
  }
1677
3352
 
3353
+
3354
+
1678
3355
  return {
3356
+
1679
3357
  value: currentVal,
3358
+
1680
3359
  baseValue: baseVal,
3360
+
1681
3361
  userValue: userVal,
3362
+
1682
3363
  isOverridden,
3364
+
1683
3365
  isDirty,
3366
+
1684
3367
  invalid,
3368
+
1685
3369
  };
3370
+
1686
3371
  }
1687
3372
 
3373
+
3374
+
1688
3375
  function computeSavePlan(draft, snap) {
3376
+
1689
3377
  if (!draft) return [];
1690
3378
 
3379
+
3380
+
1691
3381
  const fields = ['maxIterations', 'autoDrive', 'enableSound', 'showQuickLaunchButton'];
3382
+
1692
3383
  const writes = [];
1693
3384
 
3385
+
3386
+
1694
3387
  for (const f of fields) {
3388
+
1695
3389
  if (draft[f] === undefined) continue;
1696
3390
 
3391
+
3392
+
1697
3393
  const status = getFieldStatus(f, draft[f], snap);
3394
+
1698
3395
  if (status.invalid) continue;
1699
3396
 
3397
+
3398
+
1700
3399
  if (status.isDirty) {
3400
+
1701
3401
  if (f === 'maxIterations' && draft[f] === '') {
3402
+
1702
3403
  writes.push([f, undefined]);
3404
+
1703
3405
  } else if (f === 'maxIterations') {
3406
+
1704
3407
  writes.push([f, parseNumberField(draft[f])]);
3408
+
1705
3409
  } else {
3410
+
1706
3411
  writes.push([f, Boolean(draft[f])]);
3412
+
1707
3413
  }
3414
+
1708
3415
  }
3416
+
1709
3417
  }
1710
3418
 
3419
+
3420
+
1711
3421
  return writes;
3422
+
1712
3423
  }
1713
3424
 
3425
+
3426
+
1714
3427
  // --- КАРТОЧКА НАСТРОЕК (SETTINGS CARD) ---
3428
+
1715
3429
  function GoalSettingsCard(props) {
3430
+
1716
3431
  const { scope, ctx } = props || {};
3432
+
1717
3433
  const [isOpen, setIsOpen] = useState(false);
3434
+
1718
3435
  const [draft, setDraft] = useState(null);
3436
+
1719
3437
  const [saving, setSaving] = useState(false);
3438
+
1720
3439
  const [failed, setFailed] = useState(false);
1721
3440
 
3441
+
3442
+
1722
3443
  const [snap, setSnap] = useState(() => (scope ? scope.getSnapshot() : { value: DEFAULT_SETTINGS, base: DEFAULT_SETTINGS, user: {} }));
1723
3444
 
3445
+
3446
+
1724
3447
  useEffect(() => {
3448
+
1725
3449
  injectStylesOnce();
3450
+
1726
3451
  }, []);
1727
3452
 
3453
+
3454
+
1728
3455
  useEffect(() => {
3456
+
1729
3457
  if (!scope || typeof scope.subscribe !== 'function') return;
3458
+
1730
3459
  return scope.subscribe((next) => {
3460
+
1731
3461
  setSnap(next || scope.getSnapshot());
3462
+
1732
3463
  });
3464
+
1733
3465
  }, [scope]);
1734
3466
 
3467
+
3468
+
1735
3469
  const resolveLocale = () => {
3470
+
1736
3471
  let active = 'en';
3472
+
1737
3473
  if (ctx?.locale?.getSnapshot) {
3474
+
1738
3475
  const snapLoc = ctx.locale.getSnapshot();
3476
+
1739
3477
  if (snapLoc?.active) {
3478
+
1740
3479
  const code = snapLoc.active.toLowerCase();
3480
+
1741
3481
  if (code.startsWith('zh')) active = 'zh';
3482
+
1742
3483
  else if (code.startsWith('ru')) active = 'ru';
3484
+
1743
3485
  else active = 'en';
3486
+
1744
3487
  }
3488
+
1745
3489
  } else if (typeof navigator !== 'undefined' && navigator.language) {
3490
+
1746
3491
  const code = navigator.language.toLowerCase();
3492
+
1747
3493
  if (code.startsWith('zh')) active = 'zh';
3494
+
1748
3495
  else if (code.startsWith('ru')) active = 'ru';
3496
+
1749
3497
  else active = 'en';
3498
+
1750
3499
  }
3500
+
1751
3501
  return active;
3502
+
1752
3503
  };
1753
3504
 
3505
+
3506
+
1754
3507
  const t = (key) => {
3508
+
1755
3509
  const active = resolveLocale();
3510
+
1756
3511
  return LOCALES[active]?.[key] || LOCALES.en[key] || LOCALES.ru?.[key] || key;
3512
+
1757
3513
  };
1758
3514
 
3515
+
3516
+
1759
3517
  const status = (snap && snap.status) || 'ready';
3518
+
1760
3519
  const maxIterStatus = getFieldStatus('maxIterations', draft?.maxIterations, snap);
3520
+
1761
3521
  const autoDriveStatus = getFieldStatus('autoDrive', draft?.autoDrive, snap);
3522
+
1762
3523
  const enableSoundStatus = getFieldStatus('enableSound', draft?.enableSound, snap);
3524
+
1763
3525
  const quickLaunchStatus = getFieldStatus('showQuickLaunchButton', draft?.showQuickLaunchButton, snap);
3526
+
1764
3527
  const toolFailStatus = getFieldStatus('consecutiveToolFailureLimit', draft?.consecutiveToolFailureLimit, snap);
3528
+
1765
3529
  const notifStatus = getFieldStatus('enableBrowserNotifications', draft?.enableBrowserNotifications, snap);
1766
3530
 
3531
+
3532
+
1767
3533
  const dirty = maxIterStatus.isDirty || autoDriveStatus.isDirty || enableSoundStatus.isDirty || quickLaunchStatus.isDirty || toolFailStatus.isDirty || notifStatus.isDirty;
3534
+
1768
3535
  const invalid = maxIterStatus.invalid || toolFailStatus.invalid;
3536
+
1769
3537
  const disabled = !scope || saving || snap?.writable === false;
1770
3538
 
3539
+
3540
+
1771
3541
  const edit = (field, val) => {
3542
+
1772
3543
  setFailed(false);
3544
+
1773
3545
  setDraft((prev) => ({
3546
+
1774
3547
  maxIterations: prev?.maxIterations !== undefined ? prev.maxIterations : maxIterStatus.value,
3548
+
1775
3549
  autoDrive: prev?.autoDrive !== undefined ? prev.autoDrive : autoDriveStatus.value,
3550
+
1776
3551
  enableSound: prev?.enableSound !== undefined ? prev.enableSound : enableSoundStatus.value,
3552
+
1777
3553
  showQuickLaunchButton: prev?.showQuickLaunchButton !== undefined ? prev.showQuickLaunchButton : quickLaunchStatus.value,
3554
+
1778
3555
  consecutiveToolFailureLimit: prev?.consecutiveToolFailureLimit !== undefined ? prev.consecutiveToolFailureLimit : toolFailStatus.value,
3556
+
1779
3557
  enableBrowserNotifications: prev?.enableBrowserNotifications !== undefined ? prev.enableBrowserNotifications : notifStatus.value,
3558
+
1780
3559
  [field]: val,
3560
+
1781
3561
  }));
3562
+
1782
3563
  };
1783
3564
 
3565
+
3566
+
1784
3567
  const resetFieldToDefault = (field) => {
3568
+
1785
3569
  if (field === 'maxIterations') {
3570
+
1786
3571
  edit(field, '');
3572
+
1787
3573
  } else {
3574
+
1788
3575
  edit(field, snap?.base?.[field] ?? DEFAULT_SETTINGS[field]);
3576
+
1789
3577
  }
3578
+
1790
3579
  };
1791
3580
 
3581
+
3582
+
1792
3583
  const save = async () => {
3584
+
1793
3585
  if (!scope || !dirty || invalid) return;
3586
+
1794
3587
  setSaving(true);
3588
+
1795
3589
  setFailed(false);
1796
3590
 
3591
+
3592
+
1797
3593
  const writes = computeSavePlan(draft, snap);
3594
+
1798
3595
  let landed = true;
1799
3596
 
3597
+
3598
+
1800
3599
  for (const [field, fieldValue] of writes) {
3600
+
1801
3601
  try {
3602
+
1802
3603
  await scope.set(field, fieldValue);
3604
+
1803
3605
  } catch (err) {
3606
+
1804
3607
  console.error('[dsh-goal] failed to save setting', field, err);
3608
+
1805
3609
  landed = false;
3610
+
1806
3611
  }
3612
+
1807
3613
  }
1808
3614
 
3615
+
3616
+
1809
3617
  const fresh = scope.getSnapshot();
3618
+
1810
3619
  const user = fresh.user || {};
3620
+
1811
3621
  for (const [field, fieldValue] of writes) {
3622
+
1812
3623
  if (fieldValue === undefined) {
3624
+
1813
3625
  if (user[field] !== undefined) landed = false;
3626
+
1814
3627
  } else {
3628
+
1815
3629
  if (user[field] !== fieldValue) landed = false;
3630
+
1816
3631
  }
3632
+
1817
3633
  }
1818
3634
 
3635
+
3636
+
1819
3637
  setSnap(fresh);
3638
+
1820
3639
  setSaving(false);
3640
+
1821
3641
  if (landed) {
3642
+
1822
3643
  setDraft(null);
3644
+
1823
3645
  } else {
3646
+
1824
3647
  setFailed(true);
3648
+
1825
3649
  }
3650
+
1826
3651
  };
1827
3652
 
3653
+
3654
+
1828
3655
  const Chevron = (Primitives && Primitives.IconChevronDownOutline14) || IconChevronDown;
1829
3656
 
3657
+
3658
+
1830
3659
  return React.createElement(
3660
+
1831
3661
  'li',
3662
+
1832
3663
  { className: 'dsh-goal-card' },
3664
+
1833
3665
  React.createElement(
3666
+
1834
3667
  'button',
3668
+
1835
3669
  {
3670
+
1836
3671
  className: 'dsh-goal-card-head',
3672
+
1837
3673
  onClick: () => setIsOpen(!isOpen),
3674
+
1838
3675
  'aria-expanded': isOpen,
3676
+
1839
3677
  },
3678
+
1840
3679
  React.createElement(
3680
+
1841
3681
  'div',
3682
+
1842
3683
  { style: { flex: 1 } },
1843
- React.createElement('div', { className: 'dsh-goal-card-title' }, t('pluginTitle') || 'Цели и автономный режим (Goal Mode)'),
1844
- React.createElement('div', { className: 'dsh-goal-card-sub' }, t('pluginDesc') || 'Верхняя закреплённая панель цели, декомпозиция вех и авто-драйв'),
3684
+
3685
+ React.createElement('div', { className: 'dsh-goal-card-title' }, t('pluginTitle') || 'Goal Mode & Autonomous Loop'),
3686
+
3687
+ React.createElement('div', { className: 'dsh-goal-card-sub' }, t('pluginDesc') || 'Goal banner above composer dock, milestone decomposition, and auto-drive'),
3688
+
1845
3689
  ),
3690
+
1846
3691
  React.createElement(Chevron, { className: `dsh-goal-chev ${isOpen ? 'dsh-goal-chev-open' : ''}` }),
3692
+
1847
3693
  ),
3694
+
1848
3695
  isOpen
3696
+
1849
3697
  ? React.createElement(
3698
+
1850
3699
  'div',
3700
+
1851
3701
  { className: 'dsh-goal-card-body' },
3702
+
1852
3703
  status === 'loading'
1853
- ? React.createElement('p', { className: 'dsh-goal-field-hint', style: { padding: '12px 0' } }, t('loading') || 'Загрузка настроек…')
3704
+
3705
+ ? React.createElement('p', { className: 'dsh-goal-field-hint', style: { padding: '12px 0' } }, t('loading') || 'Loading settings…')
3706
+
1854
3707
  : status !== 'ready'
1855
- ? React.createElement('p', { className: 'dsh-goal-foot-note', style: { padding: '12px 0' } }, t('unavailable') || 'Настройки недоступны')
3708
+
3709
+ ? React.createElement('p', { className: 'dsh-goal-foot-note', style: { padding: '12px 0' } }, t('unavailable') || 'Settings unavailable')
3710
+
1856
3711
  : React.createElement(
3712
+
1857
3713
  React.Fragment,
3714
+
1858
3715
  null,
3716
+
1859
3717
  React.createElement(
3718
+
1860
3719
  'div',
3720
+
1861
3721
  { className: 'dsh-goal-card-field' },
3722
+
1862
3723
  React.createElement(
3724
+
1863
3725
  'div',
3726
+
1864
3727
  { className: 'dsh-goal-field-label-row' },
1865
- React.createElement('label', { htmlFor: 'dsh-goal-max-iter' }, t('maxIterLabel') || 'Максимум итераций (Safety Limit):'),
3728
+
3729
+ React.createElement('label', { htmlFor: 'dsh-goal-max-iter' }, t('maxIterLabel') || 'Max iterations (Safety Limit):'),
3730
+
1866
3731
  React.createElement(
3732
+
1867
3733
  'div',
3734
+
1868
3735
  { className: 'dsh-goal-field-meta' },
3736
+
1869
3737
  maxIterStatus.isOverridden
1870
- ? React.createElement('span', { className: 'dsh-goal-override-tag' }, t('overridden') || 'изменено')
3738
+
3739
+ ? React.createElement('span', { className: 'dsh-goal-override-tag' }, t('overridden') || 'overridden')
3740
+
1871
3741
  : null,
3742
+
1872
3743
  maxIterStatus.isOverridden
3744
+
1873
3745
  ? React.createElement(
3746
+
1874
3747
  'button',
3748
+
1875
3749
  {
3750
+
1876
3751
  type: 'button',
3752
+
1877
3753
  className: 'dsh-goal-btn-inline-reset',
1878
- title: t('resetField') || 'Сбросить к значению по умолчанию',
3754
+
3755
+ title: t('resetField') || 'Reset to default',
3756
+
1879
3757
  onClick: () => resetFieldToDefault('maxIterations'),
3758
+
1880
3759
  },
3760
+
1881
3761
  React.createElement(IconRotateCcw, { size: 12 }),
1882
- t('resetField') || 'По умолчанию',
3762
+
3763
+ t('resetField') || 'Default',
3764
+
1883
3765
  )
3766
+
1884
3767
  : null,
3768
+
1885
3769
  ),
3770
+
1886
3771
  ),
3772
+
1887
3773
  React.createElement('input', {
3774
+
1888
3775
  id: 'dsh-goal-max-iter',
3776
+
1889
3777
  type: 'number',
3778
+
1890
3779
  min: 1,
3780
+
1891
3781
  placeholder: String(maxIterStatus.baseValue),
3782
+
1892
3783
  className: 'dsh-goal-card-input',
3784
+
1893
3785
  value: maxIterStatus.value !== undefined ? maxIterStatus.value : '',
3786
+
1894
3787
  disabled,
3788
+
1895
3789
  'aria-invalid': invalid,
3790
+
1896
3791
  onChange: (e) => edit('maxIterations', e.target.value),
3792
+
1897
3793
  }),
3794
+
1898
3795
  ),
3796
+
1899
3797
  React.createElement(
3798
+
1900
3799
  'div',
3800
+
1901
3801
  { className: 'dsh-goal-check-row' },
3802
+
1902
3803
  React.createElement(
3804
+
1903
3805
  'label',
3806
+
1904
3807
  { className: 'dsh-goal-check' },
3808
+
1905
3809
  React.createElement('input', {
3810
+
1906
3811
  type: 'checkbox',
3812
+
1907
3813
  checked: autoDriveStatus.value === true,
3814
+
1908
3815
  disabled,
3816
+
1909
3817
  onChange: (e) => edit('autoDrive', e.target.checked),
3818
+
1910
3819
  }),
1911
- t('autoDriveLabel') || 'Авто-драйв: продолжать цикл автоматически',
3820
+
3821
+ t('autoDriveLabel') || 'Auto-drive: keep the loop running automatically',
3822
+
1912
3823
  ),
3824
+
1913
3825
  autoDriveStatus.isOverridden
3826
+
1914
3827
  ? React.createElement(
3828
+
1915
3829
  'button',
3830
+
1916
3831
  {
3832
+
1917
3833
  type: 'button',
3834
+
1918
3835
  className: 'dsh-goal-btn-inline-reset',
1919
- title: t('resetField') || 'Сбросить к значению по умолчанию',
3836
+
3837
+ title: t('resetField') || 'Reset to default',
3838
+
1920
3839
  onClick: () => resetFieldToDefault('autoDrive'),
3840
+
1921
3841
  },
3842
+
1922
3843
  React.createElement(IconRotateCcw, { size: 12 }),
3844
+
1923
3845
  )
3846
+
1924
3847
  : null,
3848
+
1925
3849
  ),
3850
+
1926
3851
  React.createElement(
3852
+
1927
3853
  'div',
3854
+
1928
3855
  { className: 'dsh-goal-check-row' },
3856
+
1929
3857
  React.createElement(
3858
+
1930
3859
  'label',
3860
+
1931
3861
  { className: 'dsh-goal-check' },
3862
+
1932
3863
  React.createElement('input', {
3864
+
1933
3865
  type: 'checkbox',
3866
+
1934
3867
  checked: enableSoundStatus.value === true,
3868
+
1935
3869
  disabled,
3870
+
1936
3871
  onChange: (e) => edit('enableSound', e.target.checked),
3872
+
1937
3873
  }),
1938
- t('soundLabel') || 'Звук по завершении цели',
3874
+
3875
+ t('soundLabel') || 'Sound when a goal completes',
3876
+
1939
3877
  ),
3878
+
1940
3879
  enableSoundStatus.isOverridden
3880
+
1941
3881
  ? React.createElement(
3882
+
1942
3883
  'button',
3884
+
1943
3885
  {
3886
+
1944
3887
  type: 'button',
3888
+
1945
3889
  className: 'dsh-goal-btn-inline-reset',
1946
- title: t('resetField') || 'Сбросить к значению по умолчанию',
3890
+
3891
+ title: t('resetField') || 'Reset to default',
3892
+
1947
3893
  onClick: () => resetFieldToDefault('enableSound'),
3894
+
1948
3895
  },
3896
+
1949
3897
  React.createElement(IconRotateCcw, { size: 12 }),
3898
+
1950
3899
  )
3900
+
1951
3901
  : null,
3902
+
1952
3903
  ),
3904
+
1953
3905
  React.createElement(
3906
+
1954
3907
  'div',
3908
+
1955
3909
  { className: 'dsh-goal-check-row' },
3910
+
1956
3911
  React.createElement(
3912
+
1957
3913
  'label',
3914
+
1958
3915
  { className: 'dsh-goal-check' },
3916
+
1959
3917
  React.createElement('input', {
3918
+
1960
3919
  type: 'checkbox',
3920
+
1961
3921
  checked: quickLaunchStatus.value !== false,
3922
+
1962
3923
  disabled,
3924
+
1963
3925
  onChange: (e) => edit('showQuickLaunchButton', e.target.checked),
3926
+
1964
3927
  }),
1965
- t('quickLaunchLabel') || 'Кнопка быстрого запуска цели над полем ввода',
3928
+
3929
+ t('quickLaunchLabel') || 'Quick launch goal button above composer dock',
3930
+
1966
3931
  ),
3932
+
1967
3933
  quickLaunchStatus.isOverridden
3934
+
1968
3935
  ? React.createElement(
3936
+
1969
3937
  'button',
3938
+
1970
3939
  {
3940
+
1971
3941
  type: 'button',
3942
+
1972
3943
  className: 'dsh-goal-btn-inline-reset',
1973
- title: t('resetField') || 'Сбросить к значению по умолчанию',
3944
+
3945
+ title: t('resetField') || 'Reset to default',
3946
+
1974
3947
  onClick: () => resetFieldToDefault('showQuickLaunchButton'),
3948
+
1975
3949
  },
3950
+
1976
3951
  React.createElement(IconRotateCcw, { size: 12 }),
3952
+
1977
3953
  )
3954
+
1978
3955
  : null,
3956
+
1979
3957
  ),
3958
+
1980
3959
  React.createElement(
3960
+
1981
3961
  'div',
3962
+
1982
3963
  { className: 'dsh-goal-card-field' },
3964
+
1983
3965
  React.createElement(
3966
+
1984
3967
  'div',
3968
+
1985
3969
  { className: 'dsh-goal-field-label-row' },
1986
- React.createElement('label', { htmlFor: 'dsh-goal-tool-fail' }, t('toolFailLabel') || 'Лимит ошибок инструментов (Tool-Failure Breaker):'),
3970
+
3971
+ React.createElement('label', { htmlFor: 'dsh-goal-tool-fail' }, t('toolFailLabel') || 'Tool-Failure Breaker Limit:'),
3972
+
1987
3973
  React.createElement(
3974
+
1988
3975
  'div',
3976
+
1989
3977
  { className: 'dsh-goal-field-meta' },
3978
+
1990
3979
  toolFailStatus.isOverridden
1991
- ? React.createElement('span', { className: 'dsh-goal-override-tag' }, t('overridden') || 'изменено')
3980
+
3981
+ ? React.createElement('span', { className: 'dsh-goal-override-tag' }, t('overridden') || 'overridden')
3982
+
1992
3983
  : null,
3984
+
1993
3985
  toolFailStatus.isOverridden
3986
+
1994
3987
  ? React.createElement(
3988
+
1995
3989
  'button',
3990
+
1996
3991
  {
3992
+
1997
3993
  type: 'button',
3994
+
1998
3995
  className: 'dsh-goal-btn-inline-reset',
1999
- title: t('resetField') || 'Сбросить к значению по умолчанию',
3996
+
3997
+ title: t('resetField') || 'Reset to default',
3998
+
2000
3999
  onClick: () => edit('consecutiveToolFailureLimit', snap?.base?.consecutiveToolFailureLimit ?? DEFAULT_SETTINGS.consecutiveToolFailureLimit),
4000
+
2001
4001
  },
4002
+
2002
4003
  React.createElement(IconRotateCcw, { size: 12 }),
2003
- t('resetField') || 'По умолчанию',
4004
+
4005
+ t('resetField') || 'Default',
4006
+
2004
4007
  )
4008
+
2005
4009
  : null,
4010
+
2006
4011
  ),
4012
+
2007
4013
  ),
4014
+
2008
4015
  React.createElement('input', {
4016
+
2009
4017
  id: 'dsh-goal-tool-fail',
4018
+
2010
4019
  type: 'number',
4020
+
2011
4021
  min: 0,
4022
+
2012
4023
  placeholder: String(toolFailStatus.baseValue),
4024
+
2013
4025
  value: toolFailStatus.value !== undefined ? String(toolFailStatus.value) : '',
4026
+
2014
4027
  disabled,
4028
+
2015
4029
  className: 'dsh-goal-card-input',
4030
+
2016
4031
  onChange: (e) => edit('consecutiveToolFailureLimit', parseNumberField(e.target.value)),
4032
+
2017
4033
  }),
4034
+
2018
4035
  React.createElement(
4036
+
2019
4037
  'p',
4038
+
2020
4039
  { className: 'dsh-goal-field-hint' },
2021
- t('toolFailHint') || 'Приостанавливать цель при N ошибках инструментов подряд (0 — отключить)',
4040
+
4041
+ t('toolFailHint') || 'Auto-pause goal if N consecutive turns hit tool execution errors (0 to disable)',
4042
+
2022
4043
  ),
4044
+
2023
4045
  ),
4046
+
2024
4047
  React.createElement(
4048
+
2025
4049
  'div',
4050
+
2026
4051
  { className: 'dsh-goal-check-row' },
4052
+
2027
4053
  React.createElement(
4054
+
2028
4055
  'label',
4056
+
2029
4057
  { className: 'dsh-goal-check' },
4058
+
2030
4059
  React.createElement('input', {
4060
+
2031
4061
  type: 'checkbox',
4062
+
2032
4063
  checked: notifStatus.value !== false,
4064
+
2033
4065
  disabled,
4066
+
2034
4067
  onChange: (e) => {
4068
+
2035
4069
  edit('enableBrowserNotifications', e.target.checked);
4070
+
2036
4071
  if (e.target.checked && typeof window !== 'undefined' && 'Notification' in window && Notification.permission === 'default') {
4072
+
2037
4073
  Notification.requestPermission();
4074
+
2038
4075
  }
4076
+
2039
4077
  },
4078
+
2040
4079
  }),
2041
- t('notifLabel') || 'Браузерные уведомления при завершении цели',
4080
+
4081
+ t('notifLabel') || 'Native browser desktop notifications on completion',
4082
+
2042
4083
  ),
4084
+
2043
4085
  notifStatus.isOverridden
4086
+
2044
4087
  ? React.createElement(
4088
+
2045
4089
  'button',
4090
+
2046
4091
  {
4092
+
2047
4093
  type: 'button',
4094
+
2048
4095
  className: 'dsh-goal-btn-inline-reset',
2049
- title: t('resetField') || 'Сбросить к значению по умолчанию',
4096
+
4097
+ title: t('resetField') || 'Reset to default',
4098
+
2050
4099
  onClick: () => resetFieldToDefault('enableBrowserNotifications'),
4100
+
2051
4101
  },
4102
+
2052
4103
  React.createElement(IconRotateCcw, { size: 12 }),
4104
+
2053
4105
  )
4106
+
2054
4107
  : null,
4108
+
2055
4109
  ),
4110
+
2056
4111
  React.createElement(
4112
+
2057
4113
  'div',
4114
+
2058
4115
  { className: 'dsh-goal-foot' },
4116
+
2059
4117
  failed
2060
- ? React.createElement('span', { className: 'dsh-goal-foot-note' }, t('saveFailed') || 'Не сохранено — исправьте и повторите')
4118
+
4119
+ ? React.createElement('span', { className: 'dsh-goal-foot-note' }, t('saveFailed') || 'Not saved — fix the values and try again')
4120
+
2061
4121
  : null,
4122
+
2062
4123
  React.createElement(
4124
+
2063
4125
  'button',
4126
+
2064
4127
  {
4128
+
2065
4129
  className: 'dsh-goal-discard',
4130
+
2066
4131
  disabled: !dirty || disabled,
4132
+
2067
4133
  onClick: () => {
4134
+
2068
4135
  setFailed(false);
4136
+
2069
4137
  setDraft(null);
4138
+
2070
4139
  },
4140
+
2071
4141
  },
2072
- t('discard') || 'Отменить правки',
4142
+
4143
+ t('discard') || 'Discard changes',
4144
+
2073
4145
  ),
4146
+
2074
4147
  React.createElement(
4148
+
2075
4149
  'button',
4150
+
2076
4151
  { className: 'dsh-goal-save', disabled: !dirty || disabled || invalid, onClick: save },
2077
- saving ? t('saving') || 'Сохранение…' : t('save') || 'Сохранить',
4152
+
4153
+ saving ? (t('saving') || 'Saving…') : (t('save') || 'Save'),
4154
+
2078
4155
  ),
4156
+
2079
4157
  ),
4158
+
2080
4159
  ),
4160
+
2081
4161
  )
4162
+
2082
4163
  : null,
4164
+
2083
4165
  );
4166
+
2084
4167
  }
2085
4168
 
4169
+
4170
+
2086
4171
  // --- СЛОВАРИ ЛОКАЛИЗАЦИИ ---
4172
+
2087
4173
  const LOCALES = {
4174
+
2088
4175
  en: {
4176
+
2089
4177
  goalLabel: 'Current goal',
4178
+
2090
4179
  goalCompleted: 'Goal completed',
4180
+
2091
4181
  clearGoal: 'Clear goal',
4182
+
2092
4183
  closeBanner: 'Close goal banner',
4184
+
2093
4185
  pause: 'Pause',
4186
+
2094
4187
  paused: 'Paused',
4188
+
2095
4189
  resume: 'Resume',
4190
+
2096
4191
  details: 'Expand Goal Details',
4192
+
2097
4193
  modalTitle: 'Goal Plan & Status',
4194
+
2098
4195
  modalTitleCompleted: 'Goal Completed: Plan & Results',
4196
+
2099
4197
  time: 'Running time',
4198
+
2100
4199
  eta: 'ETA',
4200
+
2101
4201
  tokens: 'Tokens',
4202
+
2102
4203
  iterations: 'Iterations',
4204
+
2103
4205
  progress: 'Progress',
4206
+
2104
4207
  done: 'Done',
4208
+
2105
4209
  milestones: 'Plan of work:',
4210
+
2106
4211
  noMilestones: 'Agent is preparing the plan of work...',
4212
+
2107
4213
  close: 'Close',
4214
+
2108
4215
  pluginTitle: 'Goal Mode & Autonomous Loop',
4216
+
2109
4217
  pluginDesc: 'Goal banner above composer dock, milestone decomposition, and auto-drive',
4218
+
2110
4219
  maxIterLabel: 'Max iterations (Safety Limit):',
4220
+
2111
4221
  autoDriveLabel: 'Auto-drive: keep the loop running automatically',
4222
+
2112
4223
  soundLabel: 'Sound when a goal completes',
4224
+
2113
4225
  quickLaunchLabel: 'Quick launch goal button above composer dock',
4226
+
2114
4227
  quickLaunch: 'Start Goal',
4228
+
2115
4229
  quickLaunchTitle: 'Quick Launch Goal',
4230
+
2116
4231
  quickLaunchDesc: 'Define the objective for the agent in autonomous mode:',
4232
+
2117
4233
  quickLaunchPlaceholder: 'e.g. Implement report export and cover with unit tests...',
4234
+
2118
4235
  startGoal: 'Start Goal',
4236
+
2119
4237
  copyReport: '📋 Copy Report in Markdown',
4238
+
2120
4239
  reportCopied: '✅ Copied!',
4240
+
2121
4241
  save: 'Save',
4242
+
2122
4243
  saving: 'Saving…',
4244
+
2123
4245
  discard: 'Discard changes',
4246
+
2124
4247
  resetField: 'Default',
4248
+
2125
4249
  overridden: 'overridden',
4250
+
2126
4251
  saveFailed: 'Not saved — fix the values and try again',
4252
+
2127
4253
  loading: 'Loading settings…',
4254
+
2128
4255
  unavailable: 'Settings unavailable',
4256
+
2129
4257
  localeCode: 'en',
4258
+
2130
4259
  copyPRComment: '🐙 Copy for GitHub PR',
4260
+
2131
4261
  prCommentCopied: '✅ PR Report Copied!',
4262
+
2132
4263
  nudgePlaceholder: '💡 Clarify task or steer agent...',
4264
+
2133
4265
  sendNudge: 'Steer',
4266
+
2134
4267
  nudgeSent: '✅ Steering sent!',
4268
+
2135
4269
  copyGitDiff: 'Click to copy git diff command',
4270
+
2136
4271
  toolFailLabel: 'Tool-Failure Breaker Limit:',
4272
+
2137
4273
  toolFailHint: 'Auto-pause goal if N consecutive turns hit tool execution errors (0 to disable)',
4274
+
2138
4275
  notifLabel: 'Native browser desktop notifications on completion',
4276
+
2139
4277
  },
4278
+
2140
4279
  zh: {
4280
+
2141
4281
  goalLabel: '当前目标',
4282
+
2142
4283
  goalCompleted: '目标已完成',
4284
+
2143
4285
  clearGoal: '清除目标',
4286
+
2144
4287
  closeBanner: '关闭目标横幅',
4288
+
2145
4289
  pause: '暂停',
4290
+
2146
4291
  paused: '已暂停',
4292
+
2147
4293
  resume: '恢复',
4294
+
2148
4295
  details: '展开目标详情',
4296
+
2149
4297
  modalTitle: '目标计划与状态',
4298
+
2150
4299
  modalTitleCompleted: '目标已完成:计划与成果',
4300
+
2151
4301
  time: '运行时间',
4302
+
2152
4303
  eta: '预估剩余 (ETA)',
4304
+
2153
4305
  tokens: 'Token 消耗',
4306
+
2154
4307
  iterations: '迭代次数',
4308
+
2155
4309
  progress: '进度',
4310
+
2156
4311
  done: '已完成',
4312
+
2157
4313
  milestones: '工作计划:',
4314
+
2158
4315
  noMilestones: '智能体正在制定工作计划...',
4316
+
2159
4317
  close: '关闭',
4318
+
2160
4319
  pluginTitle: '目标模式与自主循环 (Goal Mode)',
4320
+
2161
4321
  pluginDesc: '输入框上方常驻目标横幅、里程碑拆解与自主循环',
4322
+
2162
4323
  maxIterLabel: '最大迭代次数 (Safety Limit):',
4324
+
2163
4325
  autoDriveLabel: '自动执行:在各轮次间自动保持循环',
4326
+
2164
4327
  soundLabel: '目标完成时播放提示音',
4328
+
2165
4329
  quickLaunchLabel: '在输入框上方显示快速启动目标按钮',
4330
+
2166
4331
  quickLaunch: '启动目标',
4332
+
2167
4333
  quickLaunchTitle: '快速启动目标',
4334
+
2168
4335
  quickLaunchDesc: '为自主模式下的智能体设定任务目标:',
4336
+
2169
4337
  quickLaunchPlaceholder: '例如:实现报表导出功能并编写单元测试...',
4338
+
2170
4339
  startGoal: '启动目标',
4340
+
2171
4341
  copyReport: '📋 复制 Markdown 报告',
4342
+
2172
4343
  reportCopied: '✅ 已复制!',
4344
+
2173
4345
  save: '保存',
4346
+
2174
4347
  saving: '保存中…',
4348
+
2175
4349
  discard: '放弃更改',
4350
+
2176
4351
  resetField: '恢复默认',
4352
+
2177
4353
  overridden: '已修改',
4354
+
2178
4355
  saveFailed: '保存失败 — 请检查并重试',
4356
+
2179
4357
  loading: '正在加载设置…',
4358
+
2180
4359
  unavailable: '设置不可用',
4360
+
2181
4361
  localeCode: 'zh',
4362
+
2182
4363
  copyPRComment: '🐙 复制为 GitHub PR 报告',
4364
+
2183
4365
  prCommentCopied: '✅ PR 报告已复制!',
4366
+
2184
4367
  nudgePlaceholder: '💡 补充要求或调整智能体方向...',
4368
+
2185
4369
  sendNudge: '下达指令',
4370
+
2186
4371
  nudgeSent: '✅ 指令已发送!',
4372
+
2187
4373
  copyGitDiff: '点击复制 git diff 命令',
4374
+
2188
4375
  toolFailLabel: '工具调用连续故障断路器阈值:',
4376
+
2189
4377
  toolFailHint: '连续 N 轮遭遇工具调用错误时自动暂停(0 表示禁用)',
4378
+
2190
4379
  notifLabel: '目标完成时发送浏览器桌面原生通知',
4380
+
2191
4381
  },
2192
- ru: {
2193
- goalLabel: 'Текущая цель',
2194
- goalCompleted: 'Цель выполнена',
2195
- clearGoal: 'Очистить цель',
2196
- closeBanner: 'Закрыть плашку цели',
2197
- pause: 'Приостановить',
2198
- paused: 'На паузе',
2199
- resume: 'Возобновить',
2200
- details: 'Развернуть детали цели',
2201
- modalTitle: 'План и статус цели',
2202
- modalTitleCompleted: 'Цель выполнена: план и результаты',
2203
- time: 'Время работы',
2204
- eta: 'Оценка (ETA)',
2205
- tokens: 'Токены',
2206
- iterations: 'Итерации',
2207
- progress: 'Прогресс',
2208
- done: 'Готово',
2209
- milestones: 'План работ:',
2210
- noMilestones: 'Агент формирует план работ...',
2211
- close: 'Закрыть',
2212
- pluginTitle: 'Цели и автономный режим (Goal Mode)',
2213
- pluginDesc: 'Панель цели над полем ввода, декомпозиция вех и авто-драйв',
2214
- maxIterLabel: 'Максимум итераций (Safety Limit):',
2215
- autoDriveLabel: 'Авто-драйв: продолжать цикл автоматически',
2216
- soundLabel: 'Звук по завершении цели',
2217
- quickLaunchLabel: 'Кнопка быстрого запуска цели над полем ввода',
2218
- quickLaunch: 'Запустить цель',
2219
- quickLaunchTitle: 'Быстрый запуск цели',
2220
- quickLaunchDesc: 'Сформулируйте задачу для агента в автономном режиме:',
2221
- quickLaunchPlaceholder: 'Например: Реализовать экспорт отчетов и покрыть тестами...',
2222
- startGoal: 'Запустить цель',
2223
- copyReport: '📋 Скопировать отчёт в Markdown',
2224
- reportCopied: '✅ Скопировано!',
2225
- save: 'Сохранить',
2226
- saving: 'Сохранение…',
2227
- discard: 'Отменить правки',
2228
- resetField: 'По умолчанию',
2229
- overridden: 'изменено',
2230
- saveFailed: 'Не сохранено — исправьте и повторите',
2231
- loading: 'Загрузка настроек…',
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: 'Браузерные уведомления на рабочем столе при завершении',
2243
- },
2244
- };
4382
+
4383
+ };
4384
+
4385
+
2245
4386
 
2246
4387
  module.exports.inject = ['slots', 'locale', 'settingsScope'];
4388
+
2247
4389
  module.exports.apply = function apply(ctx) {
4390
+
2248
4391
  // Регистрация локализации
4392
+
2249
4393
  try {
4394
+
2250
4395
  ctx.locale?.register?.(NS, LOCALES);
4396
+
2251
4397
  } catch (_) {}
2252
4398
 
4399
+
4400
+
2253
4401
  // Пространство настроек плагина
4402
+
2254
4403
  const settingsScope =
4404
+
2255
4405
  ctx.settingsScope && typeof ctx.settingsScope.bind === 'function'
4406
+
2256
4407
  ? ctx.settingsScope.bind({ namespace: NS })
4408
+
2257
4409
  : null;
2258
4410
 
4411
+
4412
+
2259
4413
  const registerSlotSafe = (name, entry, comp) => {
4414
+
2260
4415
  try {
4416
+
2261
4417
  if (typeof ctx.slots?.inject === 'function') {
4418
+
2262
4419
  ctx.slots.inject(name, () => {
4420
+
2263
4421
  try {
4422
+
2264
4423
  return ctx.slots.register(entry, comp);
4424
+
2265
4425
  } catch (_) {}
4426
+
2266
4427
  });
4428
+
2267
4429
  } else if (typeof ctx.slots?.register === 'function') {
4430
+
2268
4431
  ctx.slots.register(entry, comp);
4432
+
2269
4433
  }
4434
+
2270
4435
  } catch (_) {}
4436
+
2271
4437
  };
2272
4438
 
4439
+
4440
+
2273
4441
  // 1. Регистрация виджета цели над полем ввода (composer dock)
4442
+
2274
4443
  registerSlotSafe(
4444
+
2275
4445
  'conversation.input.dock',
4446
+
2276
4447
  {
4448
+
2277
4449
  name: 'conversation.input.dock',
4450
+
2278
4451
  id: '@goodandready/dsh-goal',
4452
+
2279
4453
  order: 10,
4454
+
2280
4455
  locale: NS,
4456
+
2281
4457
  inject: (slotProps) => ({ ctx, ...slotProps }),
4458
+
2282
4459
  },
4460
+
2283
4461
  GoalTopBanner,
4462
+
2284
4463
  );
2285
4464
 
4465
+
4466
+
2286
4467
  // 2. Регистрация карточки настроек
4468
+
2287
4469
  registerSlotSafe(
4470
+
2288
4471
  'settings.plugin.item',
4472
+
2289
4473
  {
4474
+
2290
4475
  name: 'settings.plugin.item',
4476
+
2291
4477
  key: NS,
4478
+
2292
4479
  locale: NS,
4480
+
2293
4481
  inject: () => ({ ctx, scope: settingsScope }),
4482
+
2294
4483
  },
4484
+
2295
4485
  GoalSettingsCard,
4486
+
2296
4487
  );
4488
+
2297
4489
  };
2298
4490
 
4491
+
4492
+
2299
4493
  return module.exports;
4494
+
2300
4495
  },
4496
+
2301
4497
  });
4498
+