@goodandready/dsh-goal 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/README.ru.md +12 -0
- package/README.zh.md +12 -0
- package/lib/client.js +4418 -4498
- package/lib/command-handler.js +242 -276
- package/lib/engine-prompt.js +57 -0
- package/lib/engine-reports.js +114 -0
- package/lib/engine-store.js +142 -0
- package/lib/goal-engine-constants.js +100 -0
- package/lib/goal-engine.js +579 -2018
- package/lib/index.js +452 -1960
- package/lib/routes.js +294 -0
- package/lib/tools.js +294 -0
- package/lib/updater.js +317 -0
- package/package.json +62 -63
package/lib/goal-engine.js
CHANGED
|
@@ -1,2018 +1,579 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
this.
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
if (this.saveTimer) {
|
|
582
|
-
|
|
583
|
-
clearTimeout(this.saveTimer);
|
|
584
|
-
|
|
585
|
-
this.saveTimer = null;
|
|
586
|
-
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
this.writeStateToDiskSync();
|
|
590
|
-
|
|
591
|
-
return;
|
|
592
|
-
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
if (!this.saveTimer) {
|
|
596
|
-
|
|
597
|
-
this.saveTimer = setTimeout(() => {
|
|
598
|
-
|
|
599
|
-
this.saveTimer = null;
|
|
600
|
-
|
|
601
|
-
this.writeStateToDiskSync();
|
|
602
|
-
|
|
603
|
-
}, 250);
|
|
604
|
-
|
|
605
|
-
if (typeof this.saveTimer.unref === 'function') {
|
|
606
|
-
|
|
607
|
-
this.saveTimer.unref();
|
|
608
|
-
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
writeStateToDiskSync() {
|
|
618
|
-
|
|
619
|
-
if (!this.storagePath) return;
|
|
620
|
-
|
|
621
|
-
try {
|
|
622
|
-
|
|
623
|
-
if (this.goals.size === 0) {
|
|
624
|
-
|
|
625
|
-
if (fs.existsSync(this.storagePath)) {
|
|
626
|
-
|
|
627
|
-
fs.unlinkSync(this.storagePath);
|
|
628
|
-
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
return;
|
|
632
|
-
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
const sessionsObj = {};
|
|
636
|
-
|
|
637
|
-
for (const [sid, goal] of this.goals.entries()) {
|
|
638
|
-
|
|
639
|
-
sessionsObj[sid] = goal;
|
|
640
|
-
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
const payload = {
|
|
644
|
-
|
|
645
|
-
version: 2,
|
|
646
|
-
|
|
647
|
-
sessions: sessionsObj,
|
|
648
|
-
|
|
649
|
-
...(this.goals.has('default') ? this.goals.get('default') : {}),
|
|
650
|
-
|
|
651
|
-
};
|
|
652
|
-
|
|
653
|
-
const dir = path.dirname(this.storagePath);
|
|
654
|
-
|
|
655
|
-
if (!fs.existsSync(dir)) {
|
|
656
|
-
|
|
657
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
const tmp = `${this.storagePath}.tmp.${Date.now()}`;
|
|
662
|
-
|
|
663
|
-
fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf8');
|
|
664
|
-
|
|
665
|
-
fs.renameSync(tmp, this.storagePath);
|
|
666
|
-
|
|
667
|
-
} catch (err) {
|
|
668
|
-
|
|
669
|
-
console.warn('[GoalEngine] Failed to write state to disk:', err);
|
|
670
|
-
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
flushSync() {
|
|
678
|
-
|
|
679
|
-
if (this.saveTimer) {
|
|
680
|
-
|
|
681
|
-
clearTimeout(this.saveTimer);
|
|
682
|
-
|
|
683
|
-
this.saveTimer = null;
|
|
684
|
-
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
this.writeStateToDiskSync();
|
|
688
|
-
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
saveStateToDisk() {
|
|
694
|
-
|
|
695
|
-
this.flushSync();
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
/**
|
|
702
|
-
|
|
703
|
-
* Подписка на изменение состояния
|
|
704
|
-
|
|
705
|
-
* @param {Function} callback
|
|
706
|
-
|
|
707
|
-
* @returns {Function} unsubscribe
|
|
708
|
-
|
|
709
|
-
*/
|
|
710
|
-
|
|
711
|
-
subscribe(callback) {
|
|
712
|
-
|
|
713
|
-
this.listeners.add(callback);
|
|
714
|
-
|
|
715
|
-
return () => this.listeners.delete(callback);
|
|
716
|
-
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
emit(sessionId = 'default', immediate = false) {
|
|
722
|
-
|
|
723
|
-
this.scheduleSave(immediate);
|
|
724
|
-
|
|
725
|
-
const sid = sessionId || 'default';
|
|
726
|
-
|
|
727
|
-
const snapshot = this.getSnapshot(sid);
|
|
728
|
-
|
|
729
|
-
for (const listener of this.listeners) {
|
|
730
|
-
|
|
731
|
-
try {
|
|
732
|
-
|
|
733
|
-
listener(snapshot, sid);
|
|
734
|
-
|
|
735
|
-
} catch (err) {
|
|
736
|
-
|
|
737
|
-
console.error('[GoalEngine] Listener error:', err);
|
|
738
|
-
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
/**
|
|
748
|
-
|
|
749
|
-
* Отслеживание прогресса для Smart Progress Guard
|
|
750
|
-
|
|
751
|
-
*/
|
|
752
|
-
|
|
753
|
-
recordProgress(sessionId = 'default') {
|
|
754
|
-
|
|
755
|
-
const sid = sessionId || 'default';
|
|
756
|
-
|
|
757
|
-
this.stallCounters.set(sid, 0);
|
|
758
|
-
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
incrementStallCount(sessionId = 'default') {
|
|
764
|
-
|
|
765
|
-
const sid = sessionId || 'default';
|
|
766
|
-
|
|
767
|
-
const current = this.stallCounters.get(sid) || 0;
|
|
768
|
-
|
|
769
|
-
const next = current + 1;
|
|
770
|
-
|
|
771
|
-
this.stallCounters.set(sid, next);
|
|
772
|
-
|
|
773
|
-
return next;
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
getStallCount(sessionId = 'default') {
|
|
780
|
-
|
|
781
|
-
const sid = sessionId || 'default';
|
|
782
|
-
|
|
783
|
-
return this.stallCounters.get(sid) || 0;
|
|
784
|
-
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
/**
|
|
790
|
-
|
|
791
|
-
* Динамическое обновление настроек на лету
|
|
792
|
-
|
|
793
|
-
* @param {Object} config
|
|
794
|
-
|
|
795
|
-
*/
|
|
796
|
-
|
|
797
|
-
updateConfig(config = {}) {
|
|
798
|
-
|
|
799
|
-
if (typeof config.defaultMaxIterations === 'number' && config.defaultMaxIterations >= 1) {
|
|
800
|
-
|
|
801
|
-
const prev = this.defaultMaxIterations;
|
|
802
|
-
|
|
803
|
-
this.defaultMaxIterations = config.defaultMaxIterations;
|
|
804
|
-
|
|
805
|
-
for (const [_, goal] of this.goals) {
|
|
806
|
-
|
|
807
|
-
if (goal && goal.maxIterations === prev) {
|
|
808
|
-
|
|
809
|
-
goal.maxIterations = config.defaultMaxIterations;
|
|
810
|
-
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
if (typeof config.autoDrive === 'boolean') {
|
|
818
|
-
|
|
819
|
-
this.autoDrive = config.autoDrive;
|
|
820
|
-
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
if (typeof config.enableSound === 'boolean') {
|
|
824
|
-
|
|
825
|
-
this.enableSound = config.enableSound;
|
|
826
|
-
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
if (typeof config.showQuickLaunchButton === 'boolean') {
|
|
830
|
-
|
|
831
|
-
this.showQuickLaunchButton = config.showQuickLaunchButton;
|
|
832
|
-
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
if (typeof config.consecutiveToolFailureLimit === 'number') {
|
|
836
|
-
|
|
837
|
-
this.consecutiveToolFailureLimit = Math.max(0, config.consecutiveToolFailureLimit);
|
|
838
|
-
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
this.emit();
|
|
842
|
-
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
/**
|
|
848
|
-
|
|
849
|
-
* Получение цели сессии
|
|
850
|
-
|
|
851
|
-
*/
|
|
852
|
-
|
|
853
|
-
getGoal(sessionId = 'default') {
|
|
854
|
-
|
|
855
|
-
return this.goals.get(sessionId || 'default') || null;
|
|
856
|
-
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
/**
|
|
862
|
-
|
|
863
|
-
* Очистка старых неактивных сессий во избежание утечек памяти
|
|
864
|
-
|
|
865
|
-
*/
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
/**
|
|
870
|
-
|
|
871
|
-
* Управление счетчиком повторяющихся ошибок инструментов
|
|
872
|
-
|
|
873
|
-
*/
|
|
874
|
-
|
|
875
|
-
incrementToolFailureCount(sessionId = 'default') {
|
|
876
|
-
|
|
877
|
-
const sid = sessionId || 'default';
|
|
878
|
-
|
|
879
|
-
const current = this.toolFailureCounters.get(sid) || 0;
|
|
880
|
-
|
|
881
|
-
const next = current + 1;
|
|
882
|
-
|
|
883
|
-
this.toolFailureCounters.set(sid, next);
|
|
884
|
-
|
|
885
|
-
return next;
|
|
886
|
-
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
resetToolFailureCount(sessionId = 'default') {
|
|
892
|
-
|
|
893
|
-
const sid = sessionId || 'default';
|
|
894
|
-
|
|
895
|
-
this.toolFailureCounters.set(sid, 0);
|
|
896
|
-
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
getToolFailureCount(sessionId = 'default') {
|
|
902
|
-
|
|
903
|
-
const sid = sessionId || 'default';
|
|
904
|
-
|
|
905
|
-
return this.toolFailureCounters.get(sid) || 0;
|
|
906
|
-
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
/**
|
|
912
|
-
|
|
913
|
-
* Живая корректировка цели / добавление уточнений (Live Steering / Nudge)
|
|
914
|
-
|
|
915
|
-
* @param {string} text
|
|
916
|
-
|
|
917
|
-
* @param {string} [sessionId='default']
|
|
918
|
-
|
|
919
|
-
*/
|
|
920
|
-
|
|
921
|
-
nudge(text, sessionId = 'default') {
|
|
922
|
-
|
|
923
|
-
const sid = sessionId || 'default';
|
|
924
|
-
|
|
925
|
-
const goal = this.goals.get(sid);
|
|
926
|
-
|
|
927
|
-
if (!goal) return this.getSnapshot(sid);
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
const clean = String(text || '').trim();
|
|
932
|
-
|
|
933
|
-
if (!clean) return this.getSnapshot(sid);
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
if (!Array.isArray(goal.nudges)) {
|
|
938
|
-
|
|
939
|
-
goal.nudges = [];
|
|
940
|
-
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
const now = Date.now();
|
|
944
|
-
|
|
945
|
-
goal.nudges.push({ text: clean, timestamp: now });
|
|
946
|
-
|
|
947
|
-
goal.pendingNudge = clean;
|
|
948
|
-
|
|
949
|
-
goal.logs.push({
|
|
950
|
-
|
|
951
|
-
timestamp: now,
|
|
952
|
-
|
|
953
|
-
type: 'info',
|
|
954
|
-
|
|
955
|
-
message: `User steering / clarification: "${clean}"`,
|
|
956
|
-
|
|
957
|
-
});
|
|
958
|
-
|
|
959
|
-
if (goal.logs.length > 100) {
|
|
960
|
-
|
|
961
|
-
goal.logs = goal.logs.slice(-100);
|
|
962
|
-
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
this.emit(sid, true);
|
|
968
|
-
|
|
969
|
-
return this.getSnapshot(sid);
|
|
970
|
-
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
consumePendingNudge(sessionId = 'default') {
|
|
976
|
-
|
|
977
|
-
const sid = sessionId || 'default';
|
|
978
|
-
|
|
979
|
-
const goal = this.goals.get(sid);
|
|
980
|
-
|
|
981
|
-
if (!goal || !goal.pendingNudge) return null;
|
|
982
|
-
|
|
983
|
-
const nudge = goal.pendingNudge;
|
|
984
|
-
|
|
985
|
-
goal.pendingNudge = null;
|
|
986
|
-
|
|
987
|
-
this.emit(sid);
|
|
988
|
-
|
|
989
|
-
return nudge;
|
|
990
|
-
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
pruneInactiveSessions() {
|
|
996
|
-
|
|
997
|
-
if (this.goals.size < this.maxSessions) return;
|
|
998
|
-
|
|
999
|
-
const inactive = [];
|
|
1000
|
-
|
|
1001
|
-
for (const [sid, goal] of this.goals.entries()) {
|
|
1002
|
-
|
|
1003
|
-
if (sid === 'default') continue;
|
|
1004
|
-
|
|
1005
|
-
if (goal.state === GoalState.COMPLETED || goal.state === GoalState.CANCELLED || goal.state === GoalState.FAILED) {
|
|
1006
|
-
|
|
1007
|
-
inactive.push({ sid, completedAt: goal.completedAt || goal.startedAt || 0 });
|
|
1008
|
-
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
inactive.sort((a, b) => a.completedAt - b.completedAt);
|
|
1014
|
-
|
|
1015
|
-
while (this.goals.size >= this.maxSessions && inactive.length > 0) {
|
|
1016
|
-
|
|
1017
|
-
const oldest = inactive.shift();
|
|
1018
|
-
|
|
1019
|
-
this.goals.delete(oldest.sid);
|
|
1020
|
-
|
|
1021
|
-
this.stallCounters.delete(oldest.sid);
|
|
1022
|
-
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
/**
|
|
1030
|
-
|
|
1031
|
-
* Запуск новой цели
|
|
1032
|
-
|
|
1033
|
-
* @param {string} title
|
|
1034
|
-
|
|
1035
|
-
* @param {Object} options
|
|
1036
|
-
|
|
1037
|
-
* @param {string} [sessionId='default']
|
|
1038
|
-
|
|
1039
|
-
*/
|
|
1040
|
-
|
|
1041
|
-
startGoal(title, options = {}, sessionId = 'default') {
|
|
1042
|
-
|
|
1043
|
-
if (!title || typeof title !== 'string' || !title.trim()) {
|
|
1044
|
-
|
|
1045
|
-
throw new Error('Goal title cannot be empty');
|
|
1046
|
-
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
this.pruneInactiveSessions();
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
const cleanTitle = title.trim();
|
|
1056
|
-
|
|
1057
|
-
const now = Date.now();
|
|
1058
|
-
|
|
1059
|
-
const sid = sessionId || 'default';
|
|
1060
|
-
|
|
1061
|
-
this.stallCounters.set(sid, 0);
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
const gitCommit = options.gitStartCommit !== undefined
|
|
1066
|
-
|
|
1067
|
-
? options.gitStartCommit
|
|
1068
|
-
|
|
1069
|
-
: getGitCurrentCommit();
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
this.toolFailureCounters.set(sid, 0);
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
const goal = {
|
|
1078
|
-
|
|
1079
|
-
id: `goal-${now}-${Math.random().toString(36).substring(2, 7)}`,
|
|
1080
|
-
|
|
1081
|
-
sessionId: sid,
|
|
1082
|
-
|
|
1083
|
-
title: cleanTitle,
|
|
1084
|
-
|
|
1085
|
-
description: options.description?.trim() || '',
|
|
1086
|
-
|
|
1087
|
-
lang: options.lang || detectLanguage(cleanTitle),
|
|
1088
|
-
|
|
1089
|
-
state: GoalState.RUNNING,
|
|
1090
|
-
|
|
1091
|
-
startedAt: now,
|
|
1092
|
-
|
|
1093
|
-
pausedAt: null,
|
|
1094
|
-
|
|
1095
|
-
totalPausedDurationMs: 0,
|
|
1096
|
-
|
|
1097
|
-
completedAt: null,
|
|
1098
|
-
|
|
1099
|
-
iterationsCount: 0,
|
|
1100
|
-
|
|
1101
|
-
maxIterations: options.maxIterations ?? this.defaultMaxIterations,
|
|
1102
|
-
|
|
1103
|
-
gitStartCommit: gitCommit || null,
|
|
1104
|
-
|
|
1105
|
-
pendingNudge: null,
|
|
1106
|
-
|
|
1107
|
-
nudges: [],
|
|
1108
|
-
|
|
1109
|
-
milestones: [],
|
|
1110
|
-
|
|
1111
|
-
tokensUsage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
1112
|
-
|
|
1113
|
-
logs: [
|
|
1114
|
-
|
|
1115
|
-
{
|
|
1116
|
-
|
|
1117
|
-
timestamp: now,
|
|
1118
|
-
|
|
1119
|
-
type: 'info',
|
|
1120
|
-
|
|
1121
|
-
message: `Goal initiated: "${cleanTitle}"` + (gitCommit ? ` (Git: ${gitCommit})` : ''),
|
|
1122
|
-
|
|
1123
|
-
},
|
|
1124
|
-
|
|
1125
|
-
],
|
|
1126
|
-
|
|
1127
|
-
resultSummary: '',
|
|
1128
|
-
|
|
1129
|
-
};
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
this.goals.set(sid, goal);
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
if (Array.isArray(options.milestones) && options.milestones.length > 0) {
|
|
1138
|
-
|
|
1139
|
-
this.addMilestones(options.milestones, false, sid);
|
|
1140
|
-
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
this.emit(sid, true);
|
|
1146
|
-
|
|
1147
|
-
return this.getSnapshot(sid);
|
|
1148
|
-
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
/**
|
|
1154
|
-
|
|
1155
|
-
* Приостановка автономного цикла цели
|
|
1156
|
-
|
|
1157
|
-
*/
|
|
1158
|
-
|
|
1159
|
-
pause(reason = 'User requested pause', sessionId = 'default') {
|
|
1160
|
-
|
|
1161
|
-
const sid = sessionId || 'default';
|
|
1162
|
-
|
|
1163
|
-
const goal = this.goals.get(sid);
|
|
1164
|
-
|
|
1165
|
-
if (!goal || goal.state !== GoalState.RUNNING) {
|
|
1166
|
-
|
|
1167
|
-
return this.getSnapshot(sid);
|
|
1168
|
-
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
goal.state = GoalState.PAUSED;
|
|
1174
|
-
|
|
1175
|
-
goal.pausedAt = Date.now();
|
|
1176
|
-
|
|
1177
|
-
goal.logs.push({
|
|
1178
|
-
|
|
1179
|
-
timestamp: Date.now(),
|
|
1180
|
-
|
|
1181
|
-
type: 'warning',
|
|
1182
|
-
|
|
1183
|
-
message: `Paused: ${reason}`,
|
|
1184
|
-
|
|
1185
|
-
});
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
if (goal.logs.length > 100) {
|
|
1190
|
-
|
|
1191
|
-
goal.logs = goal.logs.slice(-100);
|
|
1192
|
-
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
this.emit(sid, true);
|
|
1198
|
-
|
|
1199
|
-
return this.getSnapshot(sid);
|
|
1200
|
-
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
/**
|
|
1206
|
-
|
|
1207
|
-
* Возобновление выполнения цели
|
|
1208
|
-
|
|
1209
|
-
*/
|
|
1210
|
-
|
|
1211
|
-
resume(sessionId = 'default') {
|
|
1212
|
-
|
|
1213
|
-
const sid = sessionId || 'default';
|
|
1214
|
-
|
|
1215
|
-
const goal = this.goals.get(sid);
|
|
1216
|
-
|
|
1217
|
-
if (!goal || goal.state !== GoalState.PAUSED) {
|
|
1218
|
-
|
|
1219
|
-
return this.getSnapshot(sid);
|
|
1220
|
-
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
const now = Date.now();
|
|
1226
|
-
|
|
1227
|
-
if (goal.pausedAt) {
|
|
1228
|
-
|
|
1229
|
-
goal.totalPausedDurationMs += now - goal.pausedAt;
|
|
1230
|
-
|
|
1231
|
-
goal.pausedAt = null;
|
|
1232
|
-
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
goal.state = GoalState.RUNNING;
|
|
1238
|
-
|
|
1239
|
-
this.stallCounters.set(sid, 0); // сбрасываем счетчик простоя при возобновлении
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
goal.logs.push({
|
|
1244
|
-
|
|
1245
|
-
timestamp: now,
|
|
1246
|
-
|
|
1247
|
-
type: 'info',
|
|
1248
|
-
|
|
1249
|
-
message: 'Goal resumed',
|
|
1250
|
-
|
|
1251
|
-
});
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
if (goal.logs.length > 100) {
|
|
1256
|
-
|
|
1257
|
-
goal.logs = goal.logs.slice(-100);
|
|
1258
|
-
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
this.emit(sid, true);
|
|
1264
|
-
|
|
1265
|
-
return this.getSnapshot(sid);
|
|
1266
|
-
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
/**
|
|
1272
|
-
|
|
1273
|
-
* Отмена цели
|
|
1274
|
-
|
|
1275
|
-
*/
|
|
1276
|
-
|
|
1277
|
-
cancel(reason = 'Cancelled by user', sessionId = 'default') {
|
|
1278
|
-
|
|
1279
|
-
const sid = sessionId || 'default';
|
|
1280
|
-
|
|
1281
|
-
const goal = this.goals.get(sid);
|
|
1282
|
-
|
|
1283
|
-
if (!goal) return null;
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
goal.state = GoalState.CANCELLED;
|
|
1288
|
-
|
|
1289
|
-
goal.completedAt = Date.now();
|
|
1290
|
-
|
|
1291
|
-
goal.logs.push({
|
|
1292
|
-
|
|
1293
|
-
timestamp: Date.now(),
|
|
1294
|
-
|
|
1295
|
-
type: 'warning',
|
|
1296
|
-
|
|
1297
|
-
message: `Cancelled: ${reason}`,
|
|
1298
|
-
|
|
1299
|
-
});
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
if (goal.logs.length > 100) {
|
|
1304
|
-
|
|
1305
|
-
goal.logs = goal.logs.slice(-100);
|
|
1306
|
-
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
this.emit(sid, true);
|
|
1312
|
-
|
|
1313
|
-
return this.getSnapshot(sid);
|
|
1314
|
-
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
/**
|
|
1320
|
-
|
|
1321
|
-
* Очистка / сброс цели в IDLE
|
|
1322
|
-
|
|
1323
|
-
*/
|
|
1324
|
-
|
|
1325
|
-
clear(sessionId = 'default') {
|
|
1326
|
-
|
|
1327
|
-
const sid = sessionId || 'default';
|
|
1328
|
-
|
|
1329
|
-
this.goals.delete(sid);
|
|
1330
|
-
|
|
1331
|
-
this.stallCounters.delete(sid);
|
|
1332
|
-
|
|
1333
|
-
this.emit(sid, true);
|
|
1334
|
-
|
|
1335
|
-
return this.getSnapshot(sid);
|
|
1336
|
-
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
/**
|
|
1342
|
-
|
|
1343
|
-
* Успешное завершение цели
|
|
1344
|
-
|
|
1345
|
-
*/
|
|
1346
|
-
|
|
1347
|
-
completeGoal(summary = '', sessionId = 'default') {
|
|
1348
|
-
|
|
1349
|
-
const sid = sessionId || 'default';
|
|
1350
|
-
|
|
1351
|
-
const goal = this.goals.get(sid);
|
|
1352
|
-
|
|
1353
|
-
if (!goal) return null;
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
const now = Date.now();
|
|
1358
|
-
|
|
1359
|
-
goal.state = GoalState.COMPLETED;
|
|
1360
|
-
|
|
1361
|
-
goal.completedAt = now;
|
|
1362
|
-
|
|
1363
|
-
goal.resultSummary = summary;
|
|
1364
|
-
|
|
1365
|
-
goal.logs.push({
|
|
1366
|
-
|
|
1367
|
-
timestamp: now,
|
|
1368
|
-
|
|
1369
|
-
type: 'info',
|
|
1370
|
-
|
|
1371
|
-
message: `Goal completed successfully: ${summary || 'All objectives met.'}`,
|
|
1372
|
-
|
|
1373
|
-
});
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
if (goal.logs.length > 100) {
|
|
1378
|
-
|
|
1379
|
-
goal.logs = goal.logs.slice(-100);
|
|
1380
|
-
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
// Завершаем все активные milestones
|
|
1386
|
-
|
|
1387
|
-
for (const m of goal.milestones) {
|
|
1388
|
-
|
|
1389
|
-
if (m.status === MilestoneStatus.IN_PROGRESS || m.status === MilestoneStatus.PENDING) {
|
|
1390
|
-
|
|
1391
|
-
m.status = MilestoneStatus.COMPLETED;
|
|
1392
|
-
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
this.stallCounters.delete(sid);
|
|
1400
|
-
|
|
1401
|
-
this.emit(sid, true);
|
|
1402
|
-
|
|
1403
|
-
return this.getSnapshot(sid);
|
|
1404
|
-
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
/**
|
|
1410
|
-
|
|
1411
|
-
* Добавление вех (milestones)
|
|
1412
|
-
|
|
1413
|
-
*/
|
|
1414
|
-
|
|
1415
|
-
addMilestones(milestonesList, shouldEmit = true, sessionId = 'default') {
|
|
1416
|
-
|
|
1417
|
-
const sid = sessionId || 'default';
|
|
1418
|
-
|
|
1419
|
-
const goal = this.goals.get(sid);
|
|
1420
|
-
|
|
1421
|
-
if (!goal || !Array.isArray(milestonesList)) return;
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
for (const item of milestonesList) {
|
|
1426
|
-
|
|
1427
|
-
const itemTitle = typeof item === 'string' ? item : item.title;
|
|
1428
|
-
|
|
1429
|
-
if (!itemTitle || !itemTitle.trim()) continue;
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
const mId = (typeof item === 'object' && item.id) ? item.id : `m-${goal.milestones.length + 1}`;
|
|
1434
|
-
|
|
1435
|
-
goal.milestones.push({
|
|
1436
|
-
|
|
1437
|
-
id: String(mId),
|
|
1438
|
-
|
|
1439
|
-
title: itemTitle.trim(),
|
|
1440
|
-
|
|
1441
|
-
status: (typeof item === 'object' && item.status) ? item.status : MilestoneStatus.PENDING,
|
|
1442
|
-
|
|
1443
|
-
notes: (typeof item === 'object' && item.notes) ? item.notes : '',
|
|
1444
|
-
|
|
1445
|
-
});
|
|
1446
|
-
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
this.recordProgress(sid);
|
|
1452
|
-
|
|
1453
|
-
if (shouldEmit) this.emit(sid);
|
|
1454
|
-
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
/**
|
|
1460
|
-
|
|
1461
|
-
* Обновление конкретной вехи
|
|
1462
|
-
|
|
1463
|
-
*/
|
|
1464
|
-
|
|
1465
|
-
updateMilestone(id, status, notes = '', sessionId = 'default') {
|
|
1466
|
-
|
|
1467
|
-
const sid = sessionId || 'default';
|
|
1468
|
-
|
|
1469
|
-
const goal = this.goals.get(sid);
|
|
1470
|
-
|
|
1471
|
-
if (!goal) return false;
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
const target = goal.milestones.find((m) => m.id === String(id));
|
|
1476
|
-
|
|
1477
|
-
if (!target) return false;
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
if (status && Object.values(MilestoneStatus).includes(status)) {
|
|
1482
|
-
|
|
1483
|
-
target.status = status;
|
|
1484
|
-
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
if (notes) {
|
|
1488
|
-
|
|
1489
|
-
target.notes = String(notes);
|
|
1490
|
-
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
goal.logs.push({
|
|
1496
|
-
|
|
1497
|
-
timestamp: Date.now(),
|
|
1498
|
-
|
|
1499
|
-
type: 'milestone',
|
|
1500
|
-
|
|
1501
|
-
message: `Milestone [${target.title}] status -> ${target.status}`,
|
|
1502
|
-
|
|
1503
|
-
});
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
if (goal.logs.length > 100) {
|
|
1508
|
-
|
|
1509
|
-
goal.logs = goal.logs.slice(-100);
|
|
1510
|
-
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
this.recordProgress(sid);
|
|
1516
|
-
|
|
1517
|
-
this.emit(sid);
|
|
1518
|
-
|
|
1519
|
-
return true;
|
|
1520
|
-
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
/**
|
|
1526
|
-
|
|
1527
|
-
* Увеличение счётчика итераций turn
|
|
1528
|
-
|
|
1529
|
-
*/
|
|
1530
|
-
|
|
1531
|
-
incrementIteration(sessionId = 'default') {
|
|
1532
|
-
|
|
1533
|
-
const sid = sessionId || 'default';
|
|
1534
|
-
|
|
1535
|
-
const goal = this.goals.get(sid);
|
|
1536
|
-
|
|
1537
|
-
if (!goal || goal.state !== GoalState.RUNNING) {
|
|
1538
|
-
|
|
1539
|
-
return false;
|
|
1540
|
-
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
goal.iterationsCount += 1;
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
if (goal.iterationsCount >= goal.maxIterations) {
|
|
1550
|
-
|
|
1551
|
-
goal.state = GoalState.FAILED;
|
|
1552
|
-
|
|
1553
|
-
goal.logs.push({
|
|
1554
|
-
|
|
1555
|
-
timestamp: Date.now(),
|
|
1556
|
-
|
|
1557
|
-
type: 'error',
|
|
1558
|
-
|
|
1559
|
-
message: `Safety limit reached: maximum ${goal.maxIterations} iterations exceeded.`,
|
|
1560
|
-
|
|
1561
|
-
});
|
|
1562
|
-
|
|
1563
|
-
if (goal.logs.length > 100) {
|
|
1564
|
-
|
|
1565
|
-
goal.logs = goal.logs.slice(-100);
|
|
1566
|
-
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
this.emit(sid, true);
|
|
1570
|
-
|
|
1571
|
-
return false;
|
|
1572
|
-
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
this.emit(sid);
|
|
1578
|
-
|
|
1579
|
-
return true;
|
|
1580
|
-
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
/**
|
|
1586
|
-
|
|
1587
|
-
* Подсчёт времени в секундах
|
|
1588
|
-
|
|
1589
|
-
*/
|
|
1590
|
-
|
|
1591
|
-
getElapsedSeconds(sessionId = 'default') {
|
|
1592
|
-
|
|
1593
|
-
const sid = sessionId || 'default';
|
|
1594
|
-
|
|
1595
|
-
const goal = this.goals.get(sid);
|
|
1596
|
-
|
|
1597
|
-
if (!goal) return 0;
|
|
1598
|
-
|
|
1599
|
-
const { startedAt, pausedAt, totalPausedDurationMs, completedAt } = goal;
|
|
1600
|
-
|
|
1601
|
-
const endTime = completedAt || (pausedAt || Date.now());
|
|
1602
|
-
|
|
1603
|
-
const elapsedMs = Math.max(0, endTime - startedAt - totalPausedDurationMs);
|
|
1604
|
-
|
|
1605
|
-
return Math.floor(elapsedMs / 1000);
|
|
1606
|
-
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
/**
|
|
1612
|
-
|
|
1613
|
-
* Накопление статистики использования токенов сессии
|
|
1614
|
-
|
|
1615
|
-
* @param {Object} usage
|
|
1616
|
-
|
|
1617
|
-
* @param {string} [sessionId='default']
|
|
1618
|
-
|
|
1619
|
-
*/
|
|
1620
|
-
|
|
1621
|
-
addTokenUsage(usage, sessionId = 'default') {
|
|
1622
|
-
|
|
1623
|
-
if (!usage || typeof usage !== 'object') return;
|
|
1624
|
-
|
|
1625
|
-
const sid = sessionId || 'default';
|
|
1626
|
-
|
|
1627
|
-
const goal = this.goals.get(sid);
|
|
1628
|
-
|
|
1629
|
-
if (!goal) return;
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
if (!goal.tokensUsage) {
|
|
1634
|
-
|
|
1635
|
-
goal.tokensUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
1636
|
-
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
const prompt = Number(usage.promptTokens ?? usage.input_tokens ?? usage.prompt_tokens ?? 0) || 0;
|
|
1642
|
-
|
|
1643
|
-
const completion = Number(usage.completionTokens ?? usage.output_tokens ?? usage.completion_tokens ?? 0) || 0;
|
|
1644
|
-
|
|
1645
|
-
const total = Number(usage.totalTokens ?? usage.total_tokens ?? (prompt + completion)) || (prompt + completion);
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
goal.tokensUsage.promptTokens += prompt;
|
|
1650
|
-
|
|
1651
|
-
goal.tokensUsage.completionTokens += completion;
|
|
1652
|
-
|
|
1653
|
-
goal.tokensUsage.totalTokens += total;
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
this.emit(sid);
|
|
1658
|
-
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
/**
|
|
1664
|
-
|
|
1665
|
-
* Интеллектуальный расчет прогноза оставшегося времени (ETA)
|
|
1666
|
-
|
|
1667
|
-
* на основе средней скорости выполнения завершенных вех
|
|
1668
|
-
|
|
1669
|
-
* @param {string} [sessionId='default']
|
|
1670
|
-
|
|
1671
|
-
* @returns {number|null}
|
|
1672
|
-
|
|
1673
|
-
*/
|
|
1674
|
-
|
|
1675
|
-
getEstimatedRemainingSeconds(sessionId = 'default') {
|
|
1676
|
-
|
|
1677
|
-
const sid = sessionId || 'default';
|
|
1678
|
-
|
|
1679
|
-
const goal = this.goals.get(sid);
|
|
1680
|
-
|
|
1681
|
-
if (!goal || goal.state !== GoalState.RUNNING) return null;
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
const total = goal.milestones.length;
|
|
1686
|
-
|
|
1687
|
-
if (total === 0) return null;
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
const completedCount = goal.milestones.filter((m) => m.status === MilestoneStatus.COMPLETED).length;
|
|
1692
|
-
|
|
1693
|
-
if (completedCount === 0 || completedCount >= total) return null;
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
const elapsed = this.getElapsedSeconds(sid);
|
|
1698
|
-
|
|
1699
|
-
if (elapsed <= 0) return null;
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
const avgSecPerMilestone = elapsed / completedCount;
|
|
1704
|
-
|
|
1705
|
-
const remainingCount = total - completedCount;
|
|
1706
|
-
|
|
1707
|
-
return Math.max(1, Math.round(avgSecPerMilestone * remainingCount));
|
|
1708
|
-
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
/**
|
|
1714
|
-
|
|
1715
|
-
* Снимок состояния для передачи клиенту / API
|
|
1716
|
-
|
|
1717
|
-
*/
|
|
1718
|
-
|
|
1719
|
-
getSnapshot(sessionId = 'default') {
|
|
1720
|
-
|
|
1721
|
-
const sid = sessionId || 'default';
|
|
1722
|
-
|
|
1723
|
-
const goal = this.goals.get(sid);
|
|
1724
|
-
|
|
1725
|
-
if (!goal) {
|
|
1726
|
-
|
|
1727
|
-
return {
|
|
1728
|
-
|
|
1729
|
-
sessionId: sid,
|
|
1730
|
-
|
|
1731
|
-
hasActiveGoal: false,
|
|
1732
|
-
|
|
1733
|
-
state: GoalState.IDLE,
|
|
1734
|
-
|
|
1735
|
-
title: '',
|
|
1736
|
-
|
|
1737
|
-
startedAt: null,
|
|
1738
|
-
|
|
1739
|
-
pausedAt: null,
|
|
1740
|
-
|
|
1741
|
-
totalPausedDurationMs: 0,
|
|
1742
|
-
|
|
1743
|
-
completedAt: null,
|
|
1744
|
-
|
|
1745
|
-
elapsedSeconds: 0,
|
|
1746
|
-
|
|
1747
|
-
formattedElapsed: '0s',
|
|
1748
|
-
|
|
1749
|
-
estimatedRemainingSeconds: null,
|
|
1750
|
-
|
|
1751
|
-
formattedETA: null,
|
|
1752
|
-
|
|
1753
|
-
lang: 'en',
|
|
1754
|
-
|
|
1755
|
-
tokensUsage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
1756
|
-
|
|
1757
|
-
milestones: [],
|
|
1758
|
-
|
|
1759
|
-
progressPercent: 0,
|
|
1760
|
-
|
|
1761
|
-
iterationsCount: 0,
|
|
1762
|
-
|
|
1763
|
-
maxIterations: this.defaultMaxIterations,
|
|
1764
|
-
|
|
1765
|
-
autoDrive: this.autoDrive,
|
|
1766
|
-
|
|
1767
|
-
enableSound: this.enableSound,
|
|
1768
|
-
|
|
1769
|
-
showQuickLaunchButton: this.showQuickLaunchButton,
|
|
1770
|
-
|
|
1771
|
-
gitStartCommit: null,
|
|
1772
|
-
|
|
1773
|
-
pendingNudge: null,
|
|
1774
|
-
|
|
1775
|
-
toolFailureCount: 0,
|
|
1776
|
-
|
|
1777
|
-
consecutiveToolFailureLimit: this.consecutiveToolFailureLimit,
|
|
1778
|
-
|
|
1779
|
-
};
|
|
1780
|
-
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
const elapsed = this.getElapsedSeconds(sid);
|
|
1786
|
-
|
|
1787
|
-
const milestones = goal.milestones;
|
|
1788
|
-
|
|
1789
|
-
const completedCount = milestones.filter((m) => m.status === MilestoneStatus.COMPLETED).length;
|
|
1790
|
-
|
|
1791
|
-
const progressPercent = milestones.length > 0 ? Math.round((completedCount / milestones.length) * 100) : 0;
|
|
1792
|
-
|
|
1793
|
-
const estSec = this.getEstimatedRemainingSeconds(sid);
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
return {
|
|
1798
|
-
|
|
1799
|
-
sessionId: sid,
|
|
1800
|
-
|
|
1801
|
-
hasActiveGoal: true,
|
|
1802
|
-
|
|
1803
|
-
id: goal.id,
|
|
1804
|
-
|
|
1805
|
-
state: goal.state,
|
|
1806
|
-
|
|
1807
|
-
title: goal.title,
|
|
1808
|
-
|
|
1809
|
-
description: goal.description,
|
|
1810
|
-
|
|
1811
|
-
lang: goal.lang || detectLanguage(goal.title),
|
|
1812
|
-
|
|
1813
|
-
startedAt: goal.startedAt,
|
|
1814
|
-
|
|
1815
|
-
pausedAt: goal.pausedAt,
|
|
1816
|
-
|
|
1817
|
-
totalPausedDurationMs: goal.totalPausedDurationMs,
|
|
1818
|
-
|
|
1819
|
-
completedAt: goal.completedAt,
|
|
1820
|
-
|
|
1821
|
-
elapsedSeconds: elapsed,
|
|
1822
|
-
|
|
1823
|
-
formattedElapsed: formatElapsed(elapsed),
|
|
1824
|
-
|
|
1825
|
-
estimatedRemainingSeconds: estSec,
|
|
1826
|
-
|
|
1827
|
-
formattedETA: formatETA(estSec),
|
|
1828
|
-
|
|
1829
|
-
tokensUsage: goal.tokensUsage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
1830
|
-
|
|
1831
|
-
iterationsCount: goal.iterationsCount,
|
|
1832
|
-
|
|
1833
|
-
maxIterations: goal.maxIterations,
|
|
1834
|
-
|
|
1835
|
-
milestones,
|
|
1836
|
-
|
|
1837
|
-
progressPercent,
|
|
1838
|
-
|
|
1839
|
-
logs: goal.logs,
|
|
1840
|
-
|
|
1841
|
-
resultSummary: goal.resultSummary,
|
|
1842
|
-
|
|
1843
|
-
autoDrive: this.autoDrive,
|
|
1844
|
-
|
|
1845
|
-
enableSound: this.enableSound,
|
|
1846
|
-
|
|
1847
|
-
showQuickLaunchButton: this.showQuickLaunchButton,
|
|
1848
|
-
|
|
1849
|
-
gitStartCommit: goal.gitStartCommit || null,
|
|
1850
|
-
|
|
1851
|
-
pendingNudge: goal.pendingNudge || null,
|
|
1852
|
-
|
|
1853
|
-
toolFailureCount: this.getToolFailureCount(sid),
|
|
1854
|
-
|
|
1855
|
-
consecutiveToolFailureLimit: this.consecutiveToolFailureLimit,
|
|
1856
|
-
|
|
1857
|
-
};
|
|
1858
|
-
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
/**
|
|
1864
|
-
|
|
1865
|
-
* Формирование системного контекста для инжекта модели
|
|
1866
|
-
|
|
1867
|
-
*/
|
|
1868
|
-
|
|
1869
|
-
getStatePromptInjection(sessionId = 'default') {
|
|
1870
|
-
|
|
1871
|
-
const sid = sessionId || 'default';
|
|
1872
|
-
|
|
1873
|
-
const goal = this.goals.get(sid);
|
|
1874
|
-
|
|
1875
|
-
if (!goal || goal.state !== GoalState.RUNNING) {
|
|
1876
|
-
|
|
1877
|
-
return '';
|
|
1878
|
-
|
|
1879
|
-
}
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
const snapshot = this.getSnapshot(sid);
|
|
1884
|
-
|
|
1885
|
-
const lang = goal.lang || detectLanguage(snapshot.title);
|
|
1886
|
-
|
|
1887
|
-
const hasMilestones = snapshot.milestones.length > 0;
|
|
1888
|
-
|
|
1889
|
-
const etaText = snapshot.formattedETA ? ` (ETA: ${snapshot.formattedETA})` : '';
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
let nudgeText = '';
|
|
1894
|
-
|
|
1895
|
-
if (goal.pendingNudge) {
|
|
1896
|
-
|
|
1897
|
-
const userFeedback = goal.pendingNudge;
|
|
1898
|
-
|
|
1899
|
-
goal.pendingNudge = null;
|
|
1900
|
-
|
|
1901
|
-
if (lang === 'zh') {
|
|
1902
|
-
|
|
1903
|
-
nudgeText = `\n\n🚨 用户紧急补充说明 / 调整方向:\n"${userFeedback}"\n你必须根据此说明调整近期的具体执行步骤!\n`;
|
|
1904
|
-
|
|
1905
|
-
} else if (lang === 'ru') {
|
|
1906
|
-
|
|
1907
|
-
nudgeText = `\n\n🚨 СРОЧНОЕ УТОЧНЕНИЕ / НАПРАВЛЕНИЕ ОТ ПОЛЬЗОВАТЕЛЯ:\n"${userFeedback}"\nОбязательно скорректируй свои ближайшие действия с учётом этого замечания!\n`;
|
|
1908
|
-
|
|
1909
|
-
} else {
|
|
1910
|
-
|
|
1911
|
-
nudgeText = `\n\n🚨 URGENT USER CLARIFICATION / STEERING:\n"${userFeedback}"\nYou must adjust your immediate actions according to this guidance!\n`;
|
|
1912
|
-
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
}
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
if (lang === 'zh') {
|
|
1920
|
-
|
|
1921
|
-
const milestonesText = hasMilestones
|
|
1922
|
-
|
|
1923
|
-
? snapshot.milestones.map((m, i) => ` ${i + 1}. [${m.status.toUpperCase()}] ${m.title}${m.notes ? ` (${m.notes})` : ''}`).join('\n')
|
|
1924
|
-
|
|
1925
|
-
: ' (工作计划尚未建立 — 请立即调用 goal_set_milestones 设定初始里程碑!)';
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
return nudgeText + `\n\n[DSH GOAL MODE ACTIVE]
|
|
1930
|
-
|
|
1931
|
-
目标: "${snapshot.title}"
|
|
1932
|
-
|
|
1933
|
-
运行时间: ${snapshot.formattedElapsed}${etaText} | 迭代轮次: ${snapshot.iterationsCount}/${snapshot.maxIterations}
|
|
1934
|
-
|
|
1935
|
-
工作计划:
|
|
1936
|
-
|
|
1937
|
-
${milestonesText}
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
Goal Mode 执行契约 (必须严格遵循):
|
|
1942
|
-
|
|
1943
|
-
1. ${hasMilestones ? '按部就班推进当前进行中的里程碑。' : '第一步核心指令: 立即调用 goal_set_milestones 制定 3-7 个具体里程碑。在完成此工具调用前严禁执行其他操作!'}
|
|
1944
|
-
|
|
1945
|
-
2. 推进里程碑时,必须通过 goal_update_progress 工具更新状态(开始前标为 in_progress,完成后标为 completed 并附简要说明)。
|
|
1946
|
-
|
|
1947
|
-
3. 当所有里程碑全部完成后,调用 goal_finish 工具提交详细成果总结。`;
|
|
1948
|
-
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
if (lang === 'ru') {
|
|
1954
|
-
|
|
1955
|
-
const milestonesText = hasMilestones
|
|
1956
|
-
|
|
1957
|
-
? snapshot.milestones.map((m, i) => ` ${i + 1}. [${m.status.toUpperCase()}] ${m.title}${m.notes ? ` (${m.notes})` : ''}`).join('\n')
|
|
1958
|
-
|
|
1959
|
-
: ' (План работ ещё не сформирован — немедленно вызови goal_set_milestones со списком шагов!)';
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
return nudgeText + `\n\n[DSH GOAL MODE ACTIVE]
|
|
1964
|
-
|
|
1965
|
-
Цель: "${snapshot.title}"
|
|
1966
|
-
|
|
1967
|
-
Время работы: ${snapshot.formattedElapsed}${etaText} | Итерация: ${snapshot.iterationsCount}/${snapshot.maxIterations}
|
|
1968
|
-
|
|
1969
|
-
План работ:
|
|
1970
|
-
|
|
1971
|
-
${milestonesText}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
Инструкции Goal Mode (СТРОГО ОБЯЗАТЕЛЬНЫ К ВЫПОЛНЕНИЮ):
|
|
1976
|
-
|
|
1977
|
-
1. ${hasMilestones ? 'Выполняй текущий активный пункт плана работ.' : 'ТВОЙ ПЕРВЫЙ ШАГ: Немедленно вызови инструмент goal_set_milestones со списком пунктов плана работ (3-7 конкретных шагов). Запрещено выполнять работу или завершать turn без вызова goal_set_milestones!'}
|
|
1978
|
-
|
|
1979
|
-
2. По мере выполнения каждого шага обязательно отмечай его статус через инструмент goal_update_progress (status: "in_progress" перед началом шага, status: "completed" по его завершении с кратким notes).
|
|
1980
|
-
|
|
1981
|
-
3. Когда все пункты плана будут выполнены, вызови инструмент goal_finish с подробным итоговым резюме достигнутых результатов.`;
|
|
1982
|
-
|
|
1983
|
-
}
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
const milestonesText = hasMilestones
|
|
1988
|
-
|
|
1989
|
-
? snapshot.milestones.map((m, i) => ` ${i + 1}. [${m.status.toUpperCase()}] ${m.title}${m.notes ? ` (${m.notes})` : ''}`).join('\n')
|
|
1990
|
-
|
|
1991
|
-
: ' (Work plan is not yet established — call goal_set_milestones immediately with initial steps!)';
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
return nudgeText + `\n\n[DSH GOAL MODE ACTIVE]
|
|
1996
|
-
|
|
1997
|
-
Goal: "${snapshot.title}"
|
|
1998
|
-
|
|
1999
|
-
Elapsed Time: ${snapshot.formattedElapsed}${etaText} | Iteration: ${snapshot.iterationsCount}/${snapshot.maxIterations}
|
|
2000
|
-
|
|
2001
|
-
Work Plan:
|
|
2002
|
-
|
|
2003
|
-
${milestonesText}
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
Goal Mode Instructions (MANDATORY TO FOLLOW):
|
|
2008
|
-
|
|
2009
|
-
1. ${hasMilestones ? 'Execute the current active milestone from the work plan.' : 'YOUR FIRST STEP: Immediately call tool goal_set_milestones with the list of milestones (3-7 concrete steps). You must not execute work or finish turn without calling goal_set_milestones!'}
|
|
2010
|
-
|
|
2011
|
-
2. As each milestone progresses, update its status via tool goal_update_progress (status: "in_progress" before starting, status: "completed" upon completion with brief notes).
|
|
2012
|
-
|
|
2013
|
-
3. When all milestones are completed, call tool goal_finish with a detailed summary of achieved results.`;
|
|
2014
|
-
|
|
2015
|
-
}
|
|
2016
|
-
|
|
2017
|
-
}
|
|
2018
|
-
|
|
1
|
+
import { GoalState, MilestoneStatus, formatElapsed, formatETA, detectLanguage, sessionIdOf } from './goal-engine-constants.js';
|
|
2
|
+
import { getGitCurrentCommit, exportReportMarkdown, exportReportGitHubPR } from './engine-reports.js';
|
|
3
|
+
import { EngineStore } from './engine-store.js';
|
|
4
|
+
import { buildStatePromptInjection } from './engine-prompt.js';
|
|
5
|
+
|
|
6
|
+
export { GoalState, MilestoneStatus, formatElapsed, formatETA, detectLanguage, sessionIdOf };
|
|
7
|
+
export { getGitCurrentCommit, exportReportMarkdown, exportReportGitHubPR };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Isolated Goal Management Engine for DeepSeek Harness
|
|
11
|
+
*/
|
|
12
|
+
export class GoalEngine {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.defaultMaxIterations = options.defaultMaxIterations ?? 25;
|
|
15
|
+
this.autoDrive = options.autoDrive ?? true;
|
|
16
|
+
this.enableSound = options.enableSound ?? true;
|
|
17
|
+
this.showQuickLaunchButton = options.showQuickLaunchButton ?? true;
|
|
18
|
+
this.consecutiveToolFailureLimit = options.consecutiveToolFailureLimit ?? 3;
|
|
19
|
+
this.maxSessions = options.maxSessions ?? 100;
|
|
20
|
+
this.goals = new Map();
|
|
21
|
+
this.listeners = new Set();
|
|
22
|
+
this.stallCounters = new Map();
|
|
23
|
+
this.toolFailureCounters = new Map();
|
|
24
|
+
|
|
25
|
+
this.store = new EngineStore(this, options.storagePath ?? null);
|
|
26
|
+
this.store.loadStateFromDisk();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get storagePath() { return this.store.storagePath; }
|
|
30
|
+
set storagePath(val) { this.store.setStoragePath(val); }
|
|
31
|
+
get saveTimer() { return this.store.saveTimer; }
|
|
32
|
+
get currentGoal() { return this.goals.get('default') || null; }
|
|
33
|
+
set currentGoal(val) {
|
|
34
|
+
if (val) this.goals.set('default', val);
|
|
35
|
+
else this.goals.delete('default');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
loadStateFromDisk() { this.store.loadStateFromDisk(); }
|
|
39
|
+
scheduleSave(immediate = false) { this.store.scheduleSave(immediate); }
|
|
40
|
+
writeStateToDiskSync() { this.store.writeStateToDiskSync(); }
|
|
41
|
+
flushSync() { this.store.flushSync(); }
|
|
42
|
+
saveStateToDisk() { this.flushSync(); }
|
|
43
|
+
|
|
44
|
+
subscribe(callback) {
|
|
45
|
+
this.listeners.add(callback);
|
|
46
|
+
return () => this.listeners.delete(callback);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
emit(sessionId = 'default', immediate = false) {
|
|
50
|
+
this.scheduleSave(immediate);
|
|
51
|
+
const sid = sessionId || 'default';
|
|
52
|
+
const snapshot = this.getSnapshot(sid);
|
|
53
|
+
for (const listener of this.listeners) {
|
|
54
|
+
try {
|
|
55
|
+
listener(snapshot, sid);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error?.('[GoalEngine] Listener error:', err?.message || err);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
recordProgress(sessionId = 'default') {
|
|
63
|
+
const sid = sessionId || 'default';
|
|
64
|
+
this.stallCounters.set(sid, 0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
incrementStallCount(sessionId = 'default') {
|
|
68
|
+
const sid = sessionId || 'default';
|
|
69
|
+
const current = this.stallCounters.get(sid) || 0;
|
|
70
|
+
const next = current + 1;
|
|
71
|
+
this.stallCounters.set(sid, next);
|
|
72
|
+
return next;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getStallCount(sessionId = 'default') {
|
|
76
|
+
const sid = sessionId || 'default';
|
|
77
|
+
return this.stallCounters.get(sid) || 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
updateConfig(config = {}) {
|
|
81
|
+
if (typeof config.defaultMaxIterations === 'number' && config.defaultMaxIterations >= 1) {
|
|
82
|
+
const prev = this.defaultMaxIterations;
|
|
83
|
+
this.defaultMaxIterations = config.defaultMaxIterations;
|
|
84
|
+
for (const [_, goal] of this.goals) {
|
|
85
|
+
if (goal && goal.maxIterations === prev) {
|
|
86
|
+
goal.maxIterations = config.defaultMaxIterations;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (typeof config.autoDrive === 'boolean') {
|
|
91
|
+
this.autoDrive = config.autoDrive;
|
|
92
|
+
}
|
|
93
|
+
if (typeof config.enableSound === 'boolean') {
|
|
94
|
+
this.enableSound = config.enableSound;
|
|
95
|
+
}
|
|
96
|
+
if (typeof config.showQuickLaunchButton === 'boolean') {
|
|
97
|
+
this.showQuickLaunchButton = config.showQuickLaunchButton;
|
|
98
|
+
}
|
|
99
|
+
if (typeof config.consecutiveToolFailureLimit === 'number') {
|
|
100
|
+
this.consecutiveToolFailureLimit = Math.max(0, config.consecutiveToolFailureLimit);
|
|
101
|
+
}
|
|
102
|
+
this.emit();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getGoal(sessionId = 'default') {
|
|
106
|
+
return this.goals.get(sessionId || 'default') || null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
incrementToolFailureCount(sessionId = 'default') {
|
|
110
|
+
const sid = sessionId || 'default';
|
|
111
|
+
const current = this.toolFailureCounters.get(sid) || 0;
|
|
112
|
+
const next = current + 1;
|
|
113
|
+
this.toolFailureCounters.set(sid, next);
|
|
114
|
+
return next;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
resetToolFailureCount(sessionId = 'default') {
|
|
118
|
+
const sid = sessionId || 'default';
|
|
119
|
+
this.toolFailureCounters.set(sid, 0);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
getToolFailureCount(sessionId = 'default') {
|
|
123
|
+
const sid = sessionId || 'default';
|
|
124
|
+
return this.toolFailureCounters.get(sid) || 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
nudge(text, sessionId = 'default') {
|
|
128
|
+
const sid = sessionId || 'default';
|
|
129
|
+
const goal = this.goals.get(sid);
|
|
130
|
+
if (!goal) return this.getSnapshot(sid);
|
|
131
|
+
|
|
132
|
+
const clean = String(text || '').trim();
|
|
133
|
+
if (!clean) return this.getSnapshot(sid);
|
|
134
|
+
|
|
135
|
+
if (!Array.isArray(goal.nudges)) {
|
|
136
|
+
goal.nudges = [];
|
|
137
|
+
}
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
goal.nudges.push({ text: clean, timestamp: now });
|
|
140
|
+
goal.pendingNudge = clean;
|
|
141
|
+
goal.logs.push({
|
|
142
|
+
timestamp: now,
|
|
143
|
+
type: 'info',
|
|
144
|
+
message: `User steering / clarification: "${clean}"`,
|
|
145
|
+
});
|
|
146
|
+
if (goal.logs.length > 100) {
|
|
147
|
+
goal.logs = goal.logs.slice(-100);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.emit(sid, true);
|
|
151
|
+
return this.getSnapshot(sid);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
consumePendingNudge(sessionId = 'default') {
|
|
155
|
+
const sid = sessionId || 'default';
|
|
156
|
+
const goal = this.goals.get(sid);
|
|
157
|
+
if (!goal || !goal.pendingNudge) return null;
|
|
158
|
+
const nudge = goal.pendingNudge;
|
|
159
|
+
goal.pendingNudge = null;
|
|
160
|
+
this.emit(sid);
|
|
161
|
+
return nudge;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
pruneInactiveSessions() {
|
|
165
|
+
if (this.goals.size < this.maxSessions) return;
|
|
166
|
+
const inactive = [];
|
|
167
|
+
for (const [sid, goal] of this.goals.entries()) {
|
|
168
|
+
if (sid === 'default') continue;
|
|
169
|
+
if (goal.state === GoalState.COMPLETED || goal.state === GoalState.CANCELLED || goal.state === GoalState.FAILED) {
|
|
170
|
+
inactive.push({ sid, completedAt: goal.completedAt || goal.startedAt || 0 });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
inactive.sort((a, b) => a.completedAt - b.completedAt);
|
|
174
|
+
while (this.goals.size >= this.maxSessions && inactive.length > 0) {
|
|
175
|
+
const oldest = inactive.shift();
|
|
176
|
+
this.goals.delete(oldest.sid);
|
|
177
|
+
this.stallCounters.delete(oldest.sid);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
clearSession(sessionId) {
|
|
182
|
+
if (!sessionId || sessionId === 'default') return false;
|
|
183
|
+
const had = this.goals.delete(sessionId);
|
|
184
|
+
this.stallCounters.delete(sessionId);
|
|
185
|
+
this.toolFailureCounters.delete(sessionId);
|
|
186
|
+
if (had) this.emit(sessionId, true);
|
|
187
|
+
return had;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
startGoal(title, options = {}, sessionId = 'default') {
|
|
191
|
+
if (!title || typeof title !== 'string' || !title.trim()) {
|
|
192
|
+
throw new Error('Goal title cannot be empty');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.pruneInactiveSessions();
|
|
196
|
+
|
|
197
|
+
const cleanTitle = title.trim();
|
|
198
|
+
const now = Date.now();
|
|
199
|
+
const sid = sessionId || 'default';
|
|
200
|
+
this.stallCounters.set(sid, 0);
|
|
201
|
+
|
|
202
|
+
const gitCommit = options.gitStartCommit !== undefined
|
|
203
|
+
? options.gitStartCommit
|
|
204
|
+
: getGitCurrentCommit(options.cwd);
|
|
205
|
+
|
|
206
|
+
this.toolFailureCounters.set(sid, 0);
|
|
207
|
+
|
|
208
|
+
const goal = {
|
|
209
|
+
id: `goal-${now}-${Math.random().toString(36).substring(2, 7)}`,
|
|
210
|
+
sessionId: sid,
|
|
211
|
+
title: cleanTitle,
|
|
212
|
+
description: options.description?.trim() || '',
|
|
213
|
+
lang: options.lang || detectLanguage(cleanTitle),
|
|
214
|
+
state: GoalState.RUNNING,
|
|
215
|
+
startedAt: now,
|
|
216
|
+
pausedAt: null,
|
|
217
|
+
totalPausedDurationMs: 0,
|
|
218
|
+
completedAt: null,
|
|
219
|
+
iterationsCount: 0,
|
|
220
|
+
maxIterations: options.maxIterations ?? this.defaultMaxIterations,
|
|
221
|
+
gitStartCommit: gitCommit || null,
|
|
222
|
+
pendingNudge: null,
|
|
223
|
+
nudges: [],
|
|
224
|
+
milestones: [],
|
|
225
|
+
tokensUsage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
226
|
+
logs: [
|
|
227
|
+
{
|
|
228
|
+
timestamp: now,
|
|
229
|
+
type: 'info',
|
|
230
|
+
message: `Goal initiated: "${cleanTitle}"` + (gitCommit ? ` (Git: ${gitCommit})` : ''),
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
resultSummary: '',
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
this.goals.set(sid, goal);
|
|
237
|
+
|
|
238
|
+
if (Array.isArray(options.milestones) && options.milestones.length > 0) {
|
|
239
|
+
this.addMilestones(options.milestones, false, sid);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
this.emit(sid, true);
|
|
243
|
+
return this.getSnapshot(sid);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
pause(reason = 'User requested pause', sessionId = 'default') {
|
|
247
|
+
const sid = sessionId || 'default';
|
|
248
|
+
const goal = this.goals.get(sid);
|
|
249
|
+
if (!goal || goal.state !== GoalState.RUNNING) {
|
|
250
|
+
return this.getSnapshot(sid);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
goal.state = GoalState.PAUSED;
|
|
254
|
+
goal.pausedAt = Date.now();
|
|
255
|
+
goal.logs.push({
|
|
256
|
+
timestamp: Date.now(),
|
|
257
|
+
type: 'warning',
|
|
258
|
+
message: `Paused: ${reason}`,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (goal.logs.length > 100) {
|
|
262
|
+
goal.logs = goal.logs.slice(-100);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
this.emit(sid, true);
|
|
266
|
+
return this.getSnapshot(sid);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
resume(sessionId = 'default') {
|
|
270
|
+
const sid = sessionId || 'default';
|
|
271
|
+
const goal = this.goals.get(sid);
|
|
272
|
+
if (!goal || goal.state !== GoalState.PAUSED) {
|
|
273
|
+
return this.getSnapshot(sid);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const now = Date.now();
|
|
277
|
+
if (goal.pausedAt) {
|
|
278
|
+
goal.totalPausedDurationMs += now - goal.pausedAt;
|
|
279
|
+
goal.pausedAt = null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
goal.state = GoalState.RUNNING;
|
|
283
|
+
this.stallCounters.set(sid, 0);
|
|
284
|
+
|
|
285
|
+
goal.logs.push({
|
|
286
|
+
timestamp: now,
|
|
287
|
+
type: 'info',
|
|
288
|
+
message: 'Goal resumed',
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (goal.logs.length > 100) {
|
|
292
|
+
goal.logs = goal.logs.slice(-100);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
this.emit(sid, true);
|
|
296
|
+
return this.getSnapshot(sid);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
cancel(reason = 'Cancelled by user', sessionId = 'default') {
|
|
300
|
+
const sid = sessionId || 'default';
|
|
301
|
+
const goal = this.goals.get(sid);
|
|
302
|
+
if (!goal) return null;
|
|
303
|
+
|
|
304
|
+
goal.state = GoalState.CANCELLED;
|
|
305
|
+
goal.completedAt = Date.now();
|
|
306
|
+
goal.logs.push({
|
|
307
|
+
timestamp: Date.now(),
|
|
308
|
+
type: 'warning',
|
|
309
|
+
message: `Cancelled: ${reason}`,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
if (goal.logs.length > 100) {
|
|
313
|
+
goal.logs = goal.logs.slice(-100);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
this.emit(sid, true);
|
|
317
|
+
return this.getSnapshot(sid);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
clear(sessionId = 'default') {
|
|
321
|
+
const sid = sessionId || 'default';
|
|
322
|
+
this.goals.delete(sid);
|
|
323
|
+
this.stallCounters.delete(sid);
|
|
324
|
+
this.emit(sid, true);
|
|
325
|
+
return this.getSnapshot(sid);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
completeGoal(summary = '', sessionId = 'default') {
|
|
329
|
+
const sid = sessionId || 'default';
|
|
330
|
+
const goal = this.goals.get(sid);
|
|
331
|
+
if (!goal) return null;
|
|
332
|
+
|
|
333
|
+
const now = Date.now();
|
|
334
|
+
goal.state = GoalState.COMPLETED;
|
|
335
|
+
goal.completedAt = now;
|
|
336
|
+
goal.resultSummary = summary;
|
|
337
|
+
goal.logs.push({
|
|
338
|
+
timestamp: now,
|
|
339
|
+
type: 'info',
|
|
340
|
+
message: `Goal completed successfully: ${summary || 'All objectives met.'}`,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
if (goal.logs.length > 100) {
|
|
344
|
+
goal.logs = goal.logs.slice(-100);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
for (const m of goal.milestones) {
|
|
348
|
+
if (m.status === MilestoneStatus.IN_PROGRESS || m.status === MilestoneStatus.PENDING) {
|
|
349
|
+
m.status = MilestoneStatus.COMPLETED;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
this.stallCounters.delete(sid);
|
|
354
|
+
this.emit(sid, true);
|
|
355
|
+
return this.getSnapshot(sid);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
addMilestones(milestonesList, shouldEmit = true, sessionId = 'default') {
|
|
359
|
+
const sid = sessionId || 'default';
|
|
360
|
+
const goal = this.goals.get(sid);
|
|
361
|
+
if (!goal || !Array.isArray(milestonesList)) return;
|
|
362
|
+
|
|
363
|
+
for (const item of milestonesList) {
|
|
364
|
+
const itemTitle = typeof item === 'string' ? item : item.title;
|
|
365
|
+
if (!itemTitle || !itemTitle.trim()) continue;
|
|
366
|
+
|
|
367
|
+
const mId = typeof item === 'object' && item.id ? item.id : `m-${goal.milestones.length + 1}`;
|
|
368
|
+
goal.milestones.push({
|
|
369
|
+
id: String(mId),
|
|
370
|
+
title: itemTitle.trim(),
|
|
371
|
+
status: typeof item === 'object' && item.status ? item.status : MilestoneStatus.PENDING,
|
|
372
|
+
notes: typeof item === 'object' && item.notes ? item.notes : '',
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
this.recordProgress(sid);
|
|
377
|
+
if (shouldEmit) this.emit(sid);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
updateMilestone(id, status, notes = '', sessionId = 'default') {
|
|
381
|
+
const sid = sessionId || 'default';
|
|
382
|
+
const goal = this.goals.get(sid);
|
|
383
|
+
if (!goal) return false;
|
|
384
|
+
|
|
385
|
+
const target = goal.milestones.find((m) => m.id === String(id));
|
|
386
|
+
if (!target) return false;
|
|
387
|
+
|
|
388
|
+
if (status && Object.values(MilestoneStatus).includes(status)) {
|
|
389
|
+
target.status = status;
|
|
390
|
+
}
|
|
391
|
+
if (notes) {
|
|
392
|
+
target.notes = String(notes);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
goal.logs.push({
|
|
396
|
+
timestamp: Date.now(),
|
|
397
|
+
type: 'milestone',
|
|
398
|
+
message: `Milestone [${target.title}] status -> ${target.status}`,
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
if (goal.logs.length > 100) {
|
|
402
|
+
goal.logs = goal.logs.slice(-100);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
this.recordProgress(sid);
|
|
406
|
+
this.emit(sid);
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
incrementIteration(sessionId = 'default') {
|
|
411
|
+
const sid = sessionId || 'default';
|
|
412
|
+
const goal = this.goals.get(sid);
|
|
413
|
+
if (!goal || goal.state !== GoalState.RUNNING) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
goal.iterationsCount += 1;
|
|
418
|
+
|
|
419
|
+
if (goal.iterationsCount >= goal.maxIterations) {
|
|
420
|
+
goal.state = GoalState.FAILED;
|
|
421
|
+
goal.logs.push({
|
|
422
|
+
timestamp: Date.now(),
|
|
423
|
+
type: 'error',
|
|
424
|
+
message: `Safety limit reached: maximum ${goal.maxIterations} iterations exceeded.`,
|
|
425
|
+
});
|
|
426
|
+
if (goal.logs.length > 100) {
|
|
427
|
+
goal.logs = goal.logs.slice(-100);
|
|
428
|
+
}
|
|
429
|
+
this.emit(sid, true);
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
this.emit(sid);
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
getElapsedSeconds(sessionId = 'default') {
|
|
438
|
+
const sid = sessionId || 'default';
|
|
439
|
+
const goal = this.goals.get(sid);
|
|
440
|
+
if (!goal) return 0;
|
|
441
|
+
const { startedAt, pausedAt, totalPausedDurationMs, completedAt } = goal;
|
|
442
|
+
const endTime = completedAt || (pausedAt || Date.now());
|
|
443
|
+
const elapsedMs = Math.max(0, endTime - startedAt - totalPausedDurationMs);
|
|
444
|
+
return Math.floor(elapsedMs / 1000);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
addTokenUsage(usage, sessionId = 'default') {
|
|
448
|
+
if (!usage || typeof usage !== 'object') return;
|
|
449
|
+
const sid = sessionId || 'default';
|
|
450
|
+
const goal = this.goals.get(sid);
|
|
451
|
+
if (!goal) return;
|
|
452
|
+
|
|
453
|
+
if (!goal.tokensUsage) {
|
|
454
|
+
goal.tokensUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const prompt = Number(usage.promptTokens ?? usage.input_tokens ?? usage.prompt_tokens ?? 0) || 0;
|
|
458
|
+
const completion = Number(usage.completionTokens ?? usage.output_tokens ?? usage.completion_tokens ?? 0) || 0;
|
|
459
|
+
const total = Number(usage.totalTokens ?? usage.total_tokens ?? (prompt + completion)) || (prompt + completion);
|
|
460
|
+
|
|
461
|
+
goal.tokensUsage.promptTokens += prompt;
|
|
462
|
+
goal.tokensUsage.completionTokens += completion;
|
|
463
|
+
goal.tokensUsage.totalTokens += total;
|
|
464
|
+
|
|
465
|
+
this.emit(sid);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
getEstimatedRemainingSeconds(sessionId = 'default') {
|
|
469
|
+
const sid = sessionId || 'default';
|
|
470
|
+
const goal = this.goals.get(sid);
|
|
471
|
+
if (!goal || goal.state !== GoalState.RUNNING) return null;
|
|
472
|
+
|
|
473
|
+
const total = goal.milestones.length;
|
|
474
|
+
if (total === 0) return null;
|
|
475
|
+
|
|
476
|
+
const completedCount = goal.milestones.filter((m) => m.status === MilestoneStatus.COMPLETED).length;
|
|
477
|
+
if (completedCount === 0 || completedCount >= total) return null;
|
|
478
|
+
|
|
479
|
+
const elapsed = this.getElapsedSeconds(sid);
|
|
480
|
+
if (elapsed <= 0) return null;
|
|
481
|
+
|
|
482
|
+
const avgSecPerMilestone = elapsed / completedCount;
|
|
483
|
+
const remainingCount = total - completedCount;
|
|
484
|
+
return Math.max(1, Math.round(avgSecPerMilestone * remainingCount));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
getSnapshot(sessionId = 'default') {
|
|
488
|
+
const sid = sessionId || 'default';
|
|
489
|
+
const goal = this.goals.get(sid);
|
|
490
|
+
if (!goal) {
|
|
491
|
+
return {
|
|
492
|
+
sessionId: sid,
|
|
493
|
+
hasActiveGoal: false,
|
|
494
|
+
state: GoalState.IDLE,
|
|
495
|
+
title: '',
|
|
496
|
+
startedAt: null,
|
|
497
|
+
pausedAt: null,
|
|
498
|
+
totalPausedDurationMs: 0,
|
|
499
|
+
completedAt: null,
|
|
500
|
+
elapsedSeconds: 0,
|
|
501
|
+
formattedElapsed: '0s',
|
|
502
|
+
estimatedRemainingSeconds: null,
|
|
503
|
+
formattedETA: null,
|
|
504
|
+
lang: 'en',
|
|
505
|
+
tokensUsage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
506
|
+
milestones: [],
|
|
507
|
+
progressPercent: 0,
|
|
508
|
+
iterationsCount: 0,
|
|
509
|
+
maxIterations: this.defaultMaxIterations,
|
|
510
|
+
autoDrive: this.autoDrive,
|
|
511
|
+
enableSound: this.enableSound,
|
|
512
|
+
showQuickLaunchButton: this.showQuickLaunchButton,
|
|
513
|
+
gitStartCommit: null,
|
|
514
|
+
pendingNudge: null,
|
|
515
|
+
toolFailureCount: 0,
|
|
516
|
+
consecutiveToolFailureLimit: this.consecutiveToolFailureLimit,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const elapsed = this.getElapsedSeconds(sid);
|
|
521
|
+
const milestones = goal.milestones;
|
|
522
|
+
const completedCount = milestones.filter((m) => m.status === MilestoneStatus.COMPLETED).length;
|
|
523
|
+
const progressPercent = milestones.length > 0 ? Math.round((completedCount / milestones.length) * 100) : 0;
|
|
524
|
+
const estSec = this.getEstimatedRemainingSeconds(sid);
|
|
525
|
+
|
|
526
|
+
return {
|
|
527
|
+
sessionId: sid,
|
|
528
|
+
hasActiveGoal: true,
|
|
529
|
+
id: goal.id,
|
|
530
|
+
state: goal.state,
|
|
531
|
+
title: goal.title,
|
|
532
|
+
description: goal.description,
|
|
533
|
+
lang: goal.lang || detectLanguage(goal.title),
|
|
534
|
+
startedAt: goal.startedAt,
|
|
535
|
+
pausedAt: goal.pausedAt,
|
|
536
|
+
totalPausedDurationMs: goal.totalPausedDurationMs,
|
|
537
|
+
completedAt: goal.completedAt,
|
|
538
|
+
elapsedSeconds: elapsed,
|
|
539
|
+
formattedElapsed: formatElapsed(elapsed),
|
|
540
|
+
estimatedRemainingSeconds: estSec,
|
|
541
|
+
formattedETA: formatETA(estSec),
|
|
542
|
+
tokensUsage: goal.tokensUsage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
543
|
+
iterationsCount: goal.iterationsCount,
|
|
544
|
+
maxIterations: goal.maxIterations,
|
|
545
|
+
milestones,
|
|
546
|
+
progressPercent,
|
|
547
|
+
logs: goal.logs,
|
|
548
|
+
resultSummary: goal.resultSummary,
|
|
549
|
+
autoDrive: this.autoDrive,
|
|
550
|
+
enableSound: this.enableSound,
|
|
551
|
+
showQuickLaunchButton: this.showQuickLaunchButton,
|
|
552
|
+
gitStartCommit: goal.gitStartCommit || null,
|
|
553
|
+
pendingNudge: goal.pendingNudge || null,
|
|
554
|
+
toolFailureCount: this.getToolFailureCount(sid),
|
|
555
|
+
consecutiveToolFailureLimit: this.consecutiveToolFailureLimit,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
getGoalSnapshot(sessionId = 'default') {
|
|
560
|
+
return this.getSnapshot(sessionId);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
getStatePromptInjection(sessionId = 'default') {
|
|
564
|
+
const sid = sessionId || 'default';
|
|
565
|
+
const goal = this.goals.get(sid);
|
|
566
|
+
if (!goal || goal.state !== GoalState.RUNNING) {
|
|
567
|
+
return '';
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const snapshot = this.getSnapshot(sid);
|
|
571
|
+
let pendingNudge = null;
|
|
572
|
+
if (goal.pendingNudge) {
|
|
573
|
+
pendingNudge = goal.pendingNudge;
|
|
574
|
+
goal.pendingNudge = null;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return buildStatePromptInjection(snapshot, pendingNudge);
|
|
578
|
+
}
|
|
579
|
+
}
|