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