@goodandready/dsh-goal 0.1.5 → 0.1.6
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/docs/design/DESIGN.md +9 -0
- package/lib/client.js +1528 -1422
- package/lib/goal-engine.js +61 -2
- package/lib/index.js +683 -593
- package/package.json +1 -1
package/lib/goal-engine.js
CHANGED
|
@@ -51,6 +51,7 @@ export class GoalEngine {
|
|
|
51
51
|
this.goals = new Map();
|
|
52
52
|
this.listeners = new Set();
|
|
53
53
|
this.saveTimer = null;
|
|
54
|
+
this.stallCounters = new Map(); // sessionId -> number of consecutive turns without progress
|
|
54
55
|
|
|
55
56
|
const defaultStorageDir = process.env.DSH_HOME || path.join(os.homedir(), '.dsh');
|
|
56
57
|
this.storagePath = options.storagePath ?? null;
|
|
@@ -77,15 +78,45 @@ export class GoalEngine {
|
|
|
77
78
|
const raw = fs.readFileSync(this.storagePath, 'utf8');
|
|
78
79
|
const data = JSON.parse(raw);
|
|
79
80
|
if (data && typeof data === 'object') {
|
|
81
|
+
let dirty = false;
|
|
80
82
|
if (data.sessions && typeof data.sessions === 'object') {
|
|
81
83
|
for (const [sid, goal] of Object.entries(data.sessions)) {
|
|
82
84
|
if (goal && goal.id && goal.title) {
|
|
85
|
+
// Item 4: Crash Hydration — если цель осталась в RUNNING после перезапуска/падения DSH,
|
|
86
|
+
// переводим в PAUSED с понятной причиной и фиксацией времени
|
|
87
|
+
if (goal.state === GoalState.RUNNING) {
|
|
88
|
+
goal.state = GoalState.PAUSED;
|
|
89
|
+
goal.pausedAt = Date.now();
|
|
90
|
+
if (!Array.isArray(goal.logs)) goal.logs = [];
|
|
91
|
+
goal.logs.push({
|
|
92
|
+
timestamp: Date.now(),
|
|
93
|
+
type: 'warning',
|
|
94
|
+
message: 'Harness was restarted — click ▶️ to resume',
|
|
95
|
+
});
|
|
96
|
+
if (goal.logs.length > 100) goal.logs = goal.logs.slice(-100);
|
|
97
|
+
dirty = true;
|
|
98
|
+
}
|
|
83
99
|
this.goals.set(sid, goal);
|
|
84
100
|
}
|
|
85
101
|
}
|
|
86
102
|
} else if (data.id && data.title) {
|
|
103
|
+
if (data.state === GoalState.RUNNING) {
|
|
104
|
+
data.state = GoalState.PAUSED;
|
|
105
|
+
data.pausedAt = Date.now();
|
|
106
|
+
if (!Array.isArray(data.logs)) data.logs = [];
|
|
107
|
+
data.logs.push({
|
|
108
|
+
timestamp: Date.now(),
|
|
109
|
+
type: 'warning',
|
|
110
|
+
message: 'Harness was restarted — click ▶️ to resume',
|
|
111
|
+
});
|
|
112
|
+
if (data.logs.length > 100) data.logs = data.logs.slice(-100);
|
|
113
|
+
dirty = true;
|
|
114
|
+
}
|
|
87
115
|
this.goals.set('default', data);
|
|
88
116
|
}
|
|
117
|
+
if (dirty) {
|
|
118
|
+
this.scheduleSave(true);
|
|
119
|
+
}
|
|
89
120
|
}
|
|
90
121
|
}
|
|
91
122
|
} catch (err) {
|
|
@@ -175,6 +206,27 @@ export class GoalEngine {
|
|
|
175
206
|
}
|
|
176
207
|
}
|
|
177
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Отслеживание прогресса для Smart Progress Guard
|
|
211
|
+
*/
|
|
212
|
+
recordProgress(sessionId = 'default') {
|
|
213
|
+
const sid = sessionId || 'default';
|
|
214
|
+
this.stallCounters.set(sid, 0);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
incrementStallCount(sessionId = 'default') {
|
|
218
|
+
const sid = sessionId || 'default';
|
|
219
|
+
const current = this.stallCounters.get(sid) || 0;
|
|
220
|
+
const next = current + 1;
|
|
221
|
+
this.stallCounters.set(sid, next);
|
|
222
|
+
return next;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
getStallCount(sessionId = 'default') {
|
|
226
|
+
const sid = sessionId || 'default';
|
|
227
|
+
return this.stallCounters.get(sid) || 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
178
230
|
/**
|
|
179
231
|
* Динамическое обновление настроек на лету
|
|
180
232
|
* @param {Object} config
|
|
@@ -199,7 +251,7 @@ export class GoalEngine {
|
|
|
199
251
|
}
|
|
200
252
|
|
|
201
253
|
/**
|
|
202
|
-
*
|
|
254
|
+
* Получение цели сессии
|
|
203
255
|
*/
|
|
204
256
|
getGoal(sessionId = 'default') {
|
|
205
257
|
return this.goals.get(sessionId || 'default') || null;
|
|
@@ -217,11 +269,11 @@ export class GoalEngine {
|
|
|
217
269
|
inactive.push({ sid, completedAt: goal.completedAt || goal.startedAt || 0 });
|
|
218
270
|
}
|
|
219
271
|
}
|
|
220
|
-
// Сортируем от самых старых к новым
|
|
221
272
|
inactive.sort((a, b) => a.completedAt - b.completedAt);
|
|
222
273
|
while (this.goals.size >= this.maxSessions && inactive.length > 0) {
|
|
223
274
|
const oldest = inactive.shift();
|
|
224
275
|
this.goals.delete(oldest.sid);
|
|
276
|
+
this.stallCounters.delete(oldest.sid);
|
|
225
277
|
}
|
|
226
278
|
}
|
|
227
279
|
|
|
@@ -241,6 +293,7 @@ export class GoalEngine {
|
|
|
241
293
|
const cleanTitle = title.trim();
|
|
242
294
|
const now = Date.now();
|
|
243
295
|
const sid = sessionId || 'default';
|
|
296
|
+
this.stallCounters.set(sid, 0);
|
|
244
297
|
|
|
245
298
|
const goal = {
|
|
246
299
|
id: `goal-${now}-${Math.random().toString(36).substring(2, 7)}`,
|
|
@@ -318,6 +371,8 @@ export class GoalEngine {
|
|
|
318
371
|
}
|
|
319
372
|
|
|
320
373
|
goal.state = GoalState.RUNNING;
|
|
374
|
+
this.stallCounters.set(sid, 0); // сбрасываем счетчик простоя при возобновлении
|
|
375
|
+
|
|
321
376
|
goal.logs.push({
|
|
322
377
|
timestamp: now,
|
|
323
378
|
type: 'info',
|
|
@@ -362,6 +417,7 @@ export class GoalEngine {
|
|
|
362
417
|
clear(sessionId = 'default') {
|
|
363
418
|
const sid = sessionId || 'default';
|
|
364
419
|
this.goals.delete(sid);
|
|
420
|
+
this.stallCounters.delete(sid);
|
|
365
421
|
this.emit(sid, true);
|
|
366
422
|
return this.getSnapshot(sid);
|
|
367
423
|
}
|
|
@@ -395,6 +451,7 @@ export class GoalEngine {
|
|
|
395
451
|
}
|
|
396
452
|
}
|
|
397
453
|
|
|
454
|
+
this.stallCounters.delete(sid);
|
|
398
455
|
this.emit(sid, true);
|
|
399
456
|
return this.getSnapshot(sid);
|
|
400
457
|
}
|
|
@@ -420,6 +477,7 @@ export class GoalEngine {
|
|
|
420
477
|
});
|
|
421
478
|
}
|
|
422
479
|
|
|
480
|
+
this.recordProgress(sid);
|
|
423
481
|
if (shouldEmit) this.emit(sid);
|
|
424
482
|
}
|
|
425
483
|
|
|
@@ -451,6 +509,7 @@ export class GoalEngine {
|
|
|
451
509
|
goal.logs = goal.logs.slice(-100);
|
|
452
510
|
}
|
|
453
511
|
|
|
512
|
+
this.recordProgress(sid);
|
|
454
513
|
this.emit(sid);
|
|
455
514
|
return true;
|
|
456
515
|
}
|