@goodandready/dsh-goal 0.1.3 → 0.1.5
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 +7 -0
- package/lib/client.js +400 -213
- package/lib/command-handler.js +192 -189
- package/lib/goal-engine.js +255 -104
- package/lib/index.js +182 -78
- package/package.json +1 -1
package/lib/goal-engine.js
CHANGED
|
@@ -47,8 +47,10 @@ export class GoalEngine {
|
|
|
47
47
|
this.defaultMaxIterations = options.defaultMaxIterations ?? 25;
|
|
48
48
|
this.autoDrive = options.autoDrive ?? true;
|
|
49
49
|
this.enableSound = options.enableSound ?? true;
|
|
50
|
-
this.
|
|
50
|
+
this.maxSessions = options.maxSessions ?? 100;
|
|
51
|
+
this.goals = new Map();
|
|
51
52
|
this.listeners = new Set();
|
|
53
|
+
this.saveTimer = null;
|
|
52
54
|
|
|
53
55
|
const defaultStorageDir = process.env.DSH_HOME || path.join(os.homedir(), '.dsh');
|
|
54
56
|
this.storagePath = options.storagePath ?? null;
|
|
@@ -56,14 +58,34 @@ export class GoalEngine {
|
|
|
56
58
|
this.loadStateFromDisk();
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
get currentGoal() {
|
|
62
|
+
return this.goals.get('default') || null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
set currentGoal(val) {
|
|
66
|
+
if (val) {
|
|
67
|
+
this.goals.set('default', val);
|
|
68
|
+
} else {
|
|
69
|
+
this.goals.delete('default');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
59
73
|
loadStateFromDisk() {
|
|
60
74
|
if (!this.storagePath) return;
|
|
61
75
|
try {
|
|
62
76
|
if (fs.existsSync(this.storagePath)) {
|
|
63
77
|
const raw = fs.readFileSync(this.storagePath, 'utf8');
|
|
64
78
|
const data = JSON.parse(raw);
|
|
65
|
-
if (data && typeof data === 'object'
|
|
66
|
-
|
|
79
|
+
if (data && typeof data === 'object') {
|
|
80
|
+
if (data.sessions && typeof data.sessions === 'object') {
|
|
81
|
+
for (const [sid, goal] of Object.entries(data.sessions)) {
|
|
82
|
+
if (goal && goal.id && goal.title) {
|
|
83
|
+
this.goals.set(sid, goal);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} else if (data.id && data.title) {
|
|
87
|
+
this.goals.set('default', data);
|
|
88
|
+
}
|
|
67
89
|
}
|
|
68
90
|
}
|
|
69
91
|
} catch (err) {
|
|
@@ -71,19 +93,63 @@ export class GoalEngine {
|
|
|
71
93
|
}
|
|
72
94
|
}
|
|
73
95
|
|
|
74
|
-
|
|
96
|
+
scheduleSave(immediate = false) {
|
|
97
|
+
if (!this.storagePath) return;
|
|
98
|
+
if (immediate) {
|
|
99
|
+
if (this.saveTimer) {
|
|
100
|
+
clearTimeout(this.saveTimer);
|
|
101
|
+
this.saveTimer = null;
|
|
102
|
+
}
|
|
103
|
+
this.writeStateToDiskSync();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (!this.saveTimer) {
|
|
107
|
+
this.saveTimer = setTimeout(() => {
|
|
108
|
+
this.saveTimer = null;
|
|
109
|
+
this.writeStateToDiskSync();
|
|
110
|
+
}, 250);
|
|
111
|
+
if (typeof this.saveTimer.unref === 'function') {
|
|
112
|
+
this.saveTimer.unref();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
writeStateToDiskSync() {
|
|
75
118
|
if (!this.storagePath) return;
|
|
76
119
|
try {
|
|
77
|
-
if (this.
|
|
78
|
-
fs.writeFileSync(this.storagePath, JSON.stringify(this.currentGoal, null, 2), 'utf8');
|
|
79
|
-
} else {
|
|
120
|
+
if (this.goals.size === 0) {
|
|
80
121
|
if (fs.existsSync(this.storagePath)) {
|
|
81
122
|
fs.unlinkSync(this.storagePath);
|
|
82
123
|
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const sessionsObj = {};
|
|
127
|
+
for (const [sid, goal] of this.goals.entries()) {
|
|
128
|
+
sessionsObj[sid] = goal;
|
|
83
129
|
}
|
|
130
|
+
const payload = {
|
|
131
|
+
version: 2,
|
|
132
|
+
sessions: sessionsObj,
|
|
133
|
+
...(this.goals.has('default') ? this.goals.get('default') : {}),
|
|
134
|
+
};
|
|
135
|
+
const tmp = `${this.storagePath}.tmp.${Date.now()}`;
|
|
136
|
+
fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf8');
|
|
137
|
+
fs.renameSync(tmp, this.storagePath);
|
|
84
138
|
} catch (err) {
|
|
85
|
-
console.warn('[GoalEngine] Failed to
|
|
139
|
+
console.warn('[GoalEngine] Failed to write state to disk:', err);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
flushSync() {
|
|
144
|
+
if (this.saveTimer) {
|
|
145
|
+
clearTimeout(this.saveTimer);
|
|
146
|
+
this.saveTimer = null;
|
|
86
147
|
}
|
|
148
|
+
this.writeStateToDiskSync();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
saveStateToDisk() {
|
|
152
|
+
this.flushSync();
|
|
87
153
|
}
|
|
88
154
|
|
|
89
155
|
/**
|
|
@@ -96,12 +162,13 @@ export class GoalEngine {
|
|
|
96
162
|
return () => this.listeners.delete(callback);
|
|
97
163
|
}
|
|
98
164
|
|
|
99
|
-
emit() {
|
|
100
|
-
this.
|
|
101
|
-
const
|
|
165
|
+
emit(sessionId = 'default', immediate = false) {
|
|
166
|
+
this.scheduleSave(immediate);
|
|
167
|
+
const sid = sessionId || 'default';
|
|
168
|
+
const snapshot = this.getSnapshot(sid);
|
|
102
169
|
for (const listener of this.listeners) {
|
|
103
170
|
try {
|
|
104
|
-
listener(snapshot);
|
|
171
|
+
listener(snapshot, sid);
|
|
105
172
|
} catch (err) {
|
|
106
173
|
console.error('[GoalEngine] Listener error:', err);
|
|
107
174
|
}
|
|
@@ -116,8 +183,10 @@ export class GoalEngine {
|
|
|
116
183
|
if (typeof config.defaultMaxIterations === 'number' && config.defaultMaxIterations >= 1) {
|
|
117
184
|
const prev = this.defaultMaxIterations;
|
|
118
185
|
this.defaultMaxIterations = config.defaultMaxIterations;
|
|
119
|
-
|
|
120
|
-
|
|
186
|
+
for (const [_, goal] of this.goals) {
|
|
187
|
+
if (goal && goal.maxIterations === prev) {
|
|
188
|
+
goal.maxIterations = config.defaultMaxIterations;
|
|
189
|
+
}
|
|
121
190
|
}
|
|
122
191
|
}
|
|
123
192
|
if (typeof config.autoDrive === 'boolean') {
|
|
@@ -132,16 +201,50 @@ export class GoalEngine {
|
|
|
132
201
|
/**
|
|
133
202
|
* Запуск новой цели
|
|
134
203
|
*/
|
|
135
|
-
|
|
204
|
+
getGoal(sessionId = 'default') {
|
|
205
|
+
return this.goals.get(sessionId || 'default') || null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Очистка старых неактивных сессий во избежание утечек памяти
|
|
210
|
+
*/
|
|
211
|
+
pruneInactiveSessions() {
|
|
212
|
+
if (this.goals.size < this.maxSessions) return;
|
|
213
|
+
const inactive = [];
|
|
214
|
+
for (const [sid, goal] of this.goals.entries()) {
|
|
215
|
+
if (sid === 'default') continue;
|
|
216
|
+
if (goal.state === GoalState.COMPLETED || goal.state === GoalState.CANCELLED || goal.state === GoalState.FAILED) {
|
|
217
|
+
inactive.push({ sid, completedAt: goal.completedAt || goal.startedAt || 0 });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Сортируем от самых старых к новым
|
|
221
|
+
inactive.sort((a, b) => a.completedAt - b.completedAt);
|
|
222
|
+
while (this.goals.size >= this.maxSessions && inactive.length > 0) {
|
|
223
|
+
const oldest = inactive.shift();
|
|
224
|
+
this.goals.delete(oldest.sid);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Запуск новой цели
|
|
230
|
+
* @param {string} title
|
|
231
|
+
* @param {Object} options
|
|
232
|
+
* @param {string} [sessionId='default']
|
|
233
|
+
*/
|
|
234
|
+
startGoal(title, options = {}, sessionId = 'default') {
|
|
136
235
|
if (!title || typeof title !== 'string' || !title.trim()) {
|
|
137
236
|
throw new Error('Goal title cannot be empty');
|
|
138
237
|
}
|
|
139
238
|
|
|
239
|
+
this.pruneInactiveSessions();
|
|
240
|
+
|
|
140
241
|
const cleanTitle = title.trim();
|
|
141
242
|
const now = Date.now();
|
|
243
|
+
const sid = sessionId || 'default';
|
|
142
244
|
|
|
143
|
-
|
|
245
|
+
const goal = {
|
|
144
246
|
id: `goal-${now}-${Math.random().toString(36).substring(2, 7)}`,
|
|
247
|
+
sessionId: sid,
|
|
145
248
|
title: cleanTitle,
|
|
146
249
|
description: options.description?.trim() || '',
|
|
147
250
|
state: GoalState.RUNNING,
|
|
@@ -162,125 +265,154 @@ export class GoalEngine {
|
|
|
162
265
|
resultSummary: '',
|
|
163
266
|
};
|
|
164
267
|
|
|
268
|
+
this.goals.set(sid, goal);
|
|
269
|
+
|
|
165
270
|
if (Array.isArray(options.milestones) && options.milestones.length > 0) {
|
|
166
|
-
this.addMilestones(options.milestones, false);
|
|
271
|
+
this.addMilestones(options.milestones, false, sid);
|
|
167
272
|
}
|
|
168
273
|
|
|
169
|
-
this.emit();
|
|
170
|
-
return this.getSnapshot();
|
|
274
|
+
this.emit(sid, true);
|
|
275
|
+
return this.getSnapshot(sid);
|
|
171
276
|
}
|
|
172
277
|
|
|
173
278
|
/**
|
|
174
279
|
* Приостановка автономного цикла цели
|
|
175
280
|
*/
|
|
176
|
-
pause(reason = 'User requested pause') {
|
|
177
|
-
|
|
178
|
-
|
|
281
|
+
pause(reason = 'User requested pause', sessionId = 'default') {
|
|
282
|
+
const sid = sessionId || 'default';
|
|
283
|
+
const goal = this.goals.get(sid);
|
|
284
|
+
if (!goal || goal.state !== GoalState.RUNNING) {
|
|
285
|
+
return this.getSnapshot(sid);
|
|
179
286
|
}
|
|
180
287
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
288
|
+
goal.state = GoalState.PAUSED;
|
|
289
|
+
goal.pausedAt = Date.now();
|
|
290
|
+
goal.logs.push({
|
|
184
291
|
timestamp: Date.now(),
|
|
185
292
|
type: 'warning',
|
|
186
293
|
message: `Paused: ${reason}`,
|
|
187
294
|
});
|
|
188
295
|
|
|
189
|
-
|
|
190
|
-
|
|
296
|
+
if (goal.logs.length > 100) {
|
|
297
|
+
goal.logs = goal.logs.slice(-100);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
this.emit(sid, true);
|
|
301
|
+
return this.getSnapshot(sid);
|
|
191
302
|
}
|
|
192
303
|
|
|
193
304
|
/**
|
|
194
305
|
* Возобновление выполнения цели
|
|
195
306
|
*/
|
|
196
|
-
resume() {
|
|
197
|
-
|
|
198
|
-
|
|
307
|
+
resume(sessionId = 'default') {
|
|
308
|
+
const sid = sessionId || 'default';
|
|
309
|
+
const goal = this.goals.get(sid);
|
|
310
|
+
if (!goal || goal.state !== GoalState.PAUSED) {
|
|
311
|
+
return this.getSnapshot(sid);
|
|
199
312
|
}
|
|
200
313
|
|
|
201
314
|
const now = Date.now();
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
|
|
315
|
+
if (goal.pausedAt) {
|
|
316
|
+
goal.totalPausedDurationMs += now - goal.pausedAt;
|
|
317
|
+
goal.pausedAt = null;
|
|
205
318
|
}
|
|
206
319
|
|
|
207
|
-
|
|
208
|
-
|
|
320
|
+
goal.state = GoalState.RUNNING;
|
|
321
|
+
goal.logs.push({
|
|
209
322
|
timestamp: now,
|
|
210
323
|
type: 'info',
|
|
211
324
|
message: 'Goal resumed',
|
|
212
325
|
});
|
|
213
326
|
|
|
214
|
-
|
|
215
|
-
|
|
327
|
+
if (goal.logs.length > 100) {
|
|
328
|
+
goal.logs = goal.logs.slice(-100);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
this.emit(sid, true);
|
|
332
|
+
return this.getSnapshot(sid);
|
|
216
333
|
}
|
|
217
334
|
|
|
218
335
|
/**
|
|
219
336
|
* Отмена цели
|
|
220
337
|
*/
|
|
221
|
-
cancel(reason = 'Cancelled by user') {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
338
|
+
cancel(reason = 'Cancelled by user', sessionId = 'default') {
|
|
339
|
+
const sid = sessionId || 'default';
|
|
340
|
+
const goal = this.goals.get(sid);
|
|
341
|
+
if (!goal) return null;
|
|
342
|
+
|
|
343
|
+
goal.state = GoalState.CANCELLED;
|
|
344
|
+
goal.completedAt = Date.now();
|
|
345
|
+
goal.logs.push({
|
|
227
346
|
timestamp: Date.now(),
|
|
228
347
|
type: 'warning',
|
|
229
348
|
message: `Cancelled: ${reason}`,
|
|
230
349
|
});
|
|
231
350
|
|
|
232
|
-
|
|
233
|
-
|
|
351
|
+
if (goal.logs.length > 100) {
|
|
352
|
+
goal.logs = goal.logs.slice(-100);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
this.emit(sid, true);
|
|
356
|
+
return this.getSnapshot(sid);
|
|
234
357
|
}
|
|
235
358
|
|
|
236
359
|
/**
|
|
237
360
|
* Очистка / сброс цели в IDLE
|
|
238
361
|
*/
|
|
239
|
-
clear() {
|
|
240
|
-
|
|
241
|
-
this.
|
|
242
|
-
|
|
362
|
+
clear(sessionId = 'default') {
|
|
363
|
+
const sid = sessionId || 'default';
|
|
364
|
+
this.goals.delete(sid);
|
|
365
|
+
this.emit(sid, true);
|
|
366
|
+
return this.getSnapshot(sid);
|
|
243
367
|
}
|
|
244
368
|
|
|
245
369
|
/**
|
|
246
370
|
* Успешное завершение цели
|
|
247
371
|
*/
|
|
248
|
-
completeGoal(summary = '') {
|
|
249
|
-
|
|
372
|
+
completeGoal(summary = '', sessionId = 'default') {
|
|
373
|
+
const sid = sessionId || 'default';
|
|
374
|
+
const goal = this.goals.get(sid);
|
|
375
|
+
if (!goal) return null;
|
|
250
376
|
|
|
251
377
|
const now = Date.now();
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
378
|
+
goal.state = GoalState.COMPLETED;
|
|
379
|
+
goal.completedAt = now;
|
|
380
|
+
goal.resultSummary = summary;
|
|
381
|
+
goal.logs.push({
|
|
256
382
|
timestamp: now,
|
|
257
383
|
type: 'info',
|
|
258
384
|
message: `Goal completed successfully: ${summary || 'All objectives met.'}`,
|
|
259
385
|
});
|
|
260
386
|
|
|
387
|
+
if (goal.logs.length > 100) {
|
|
388
|
+
goal.logs = goal.logs.slice(-100);
|
|
389
|
+
}
|
|
390
|
+
|
|
261
391
|
// Завершаем все активные milestones
|
|
262
|
-
for (const m of
|
|
392
|
+
for (const m of goal.milestones) {
|
|
263
393
|
if (m.status === MilestoneStatus.IN_PROGRESS || m.status === MilestoneStatus.PENDING) {
|
|
264
394
|
m.status = MilestoneStatus.COMPLETED;
|
|
265
395
|
}
|
|
266
396
|
}
|
|
267
397
|
|
|
268
|
-
this.emit();
|
|
269
|
-
return this.getSnapshot();
|
|
398
|
+
this.emit(sid, true);
|
|
399
|
+
return this.getSnapshot(sid);
|
|
270
400
|
}
|
|
271
401
|
|
|
272
402
|
/**
|
|
273
403
|
* Добавление вех (milestones)
|
|
274
404
|
*/
|
|
275
|
-
addMilestones(milestonesList, shouldEmit = true) {
|
|
276
|
-
|
|
405
|
+
addMilestones(milestonesList, shouldEmit = true, sessionId = 'default') {
|
|
406
|
+
const sid = sessionId || 'default';
|
|
407
|
+
const goal = this.goals.get(sid);
|
|
408
|
+
if (!goal || !Array.isArray(milestonesList)) return;
|
|
277
409
|
|
|
278
410
|
for (const item of milestonesList) {
|
|
279
411
|
const itemTitle = typeof item === 'string' ? item : item.title;
|
|
280
412
|
if (!itemTitle || !itemTitle.trim()) continue;
|
|
281
413
|
|
|
282
|
-
const mId = (typeof item === 'object' && item.id) ? item.id : `m-${
|
|
283
|
-
|
|
414
|
+
const mId = (typeof item === 'object' && item.id) ? item.id : `m-${goal.milestones.length + 1}`;
|
|
415
|
+
goal.milestones.push({
|
|
284
416
|
id: String(mId),
|
|
285
417
|
title: itemTitle.trim(),
|
|
286
418
|
status: (typeof item === 'object' && item.status) ? item.status : MilestoneStatus.PENDING,
|
|
@@ -288,66 +420,79 @@ export class GoalEngine {
|
|
|
288
420
|
});
|
|
289
421
|
}
|
|
290
422
|
|
|
291
|
-
if (shouldEmit) this.emit();
|
|
423
|
+
if (shouldEmit) this.emit(sid);
|
|
292
424
|
}
|
|
293
425
|
|
|
294
426
|
/**
|
|
295
427
|
* Обновление конкретной вехи
|
|
296
428
|
*/
|
|
297
|
-
updateMilestone(id, status, notes = '') {
|
|
298
|
-
|
|
429
|
+
updateMilestone(id, status, notes = '', sessionId = 'default') {
|
|
430
|
+
const sid = sessionId || 'default';
|
|
431
|
+
const goal = this.goals.get(sid);
|
|
432
|
+
if (!goal) return false;
|
|
299
433
|
|
|
300
|
-
const target =
|
|
434
|
+
const target = goal.milestones.find((m) => m.id === String(id));
|
|
301
435
|
if (!target) return false;
|
|
302
436
|
|
|
303
437
|
if (status && Object.values(MilestoneStatus).includes(status)) {
|
|
304
438
|
target.status = status;
|
|
305
439
|
}
|
|
306
440
|
if (notes) {
|
|
307
|
-
target.notes = notes;
|
|
441
|
+
target.notes = String(notes);
|
|
308
442
|
}
|
|
309
443
|
|
|
310
|
-
|
|
444
|
+
goal.logs.push({
|
|
311
445
|
timestamp: Date.now(),
|
|
312
446
|
type: 'milestone',
|
|
313
447
|
message: `Milestone [${target.title}] status -> ${target.status}`,
|
|
314
448
|
});
|
|
315
449
|
|
|
316
|
-
|
|
450
|
+
if (goal.logs.length > 100) {
|
|
451
|
+
goal.logs = goal.logs.slice(-100);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
this.emit(sid);
|
|
317
455
|
return true;
|
|
318
456
|
}
|
|
319
457
|
|
|
320
458
|
/**
|
|
321
459
|
* Увеличение счётчика итераций turn
|
|
322
460
|
*/
|
|
323
|
-
incrementIteration() {
|
|
324
|
-
|
|
461
|
+
incrementIteration(sessionId = 'default') {
|
|
462
|
+
const sid = sessionId || 'default';
|
|
463
|
+
const goal = this.goals.get(sid);
|
|
464
|
+
if (!goal || goal.state !== GoalState.RUNNING) {
|
|
325
465
|
return false;
|
|
326
466
|
}
|
|
327
467
|
|
|
328
|
-
|
|
468
|
+
goal.iterationsCount += 1;
|
|
329
469
|
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
|
|
470
|
+
if (goal.iterationsCount >= goal.maxIterations) {
|
|
471
|
+
goal.state = GoalState.FAILED;
|
|
472
|
+
goal.logs.push({
|
|
333
473
|
timestamp: Date.now(),
|
|
334
474
|
type: 'error',
|
|
335
|
-
message: `Safety limit reached: maximum ${
|
|
475
|
+
message: `Safety limit reached: maximum ${goal.maxIterations} iterations exceeded.`,
|
|
336
476
|
});
|
|
337
|
-
|
|
477
|
+
if (goal.logs.length > 100) {
|
|
478
|
+
goal.logs = goal.logs.slice(-100);
|
|
479
|
+
}
|
|
480
|
+
this.emit(sid, true);
|
|
338
481
|
return false;
|
|
339
482
|
}
|
|
340
483
|
|
|
341
|
-
this.emit();
|
|
484
|
+
this.emit(sid);
|
|
342
485
|
return true;
|
|
343
486
|
}
|
|
344
487
|
|
|
345
488
|
/**
|
|
346
489
|
* Подсчёт времени в секундах
|
|
347
490
|
*/
|
|
348
|
-
getElapsedSeconds() {
|
|
349
|
-
|
|
350
|
-
const
|
|
491
|
+
getElapsedSeconds(sessionId = 'default') {
|
|
492
|
+
const sid = sessionId || 'default';
|
|
493
|
+
const goal = this.goals.get(sid);
|
|
494
|
+
if (!goal) return 0;
|
|
495
|
+
const { startedAt, pausedAt, totalPausedDurationMs, completedAt } = goal;
|
|
351
496
|
const endTime = completedAt || (pausedAt || Date.now());
|
|
352
497
|
const elapsedMs = Math.max(0, endTime - startedAt - totalPausedDurationMs);
|
|
353
498
|
return Math.floor(elapsedMs / 1000);
|
|
@@ -356,9 +501,12 @@ export class GoalEngine {
|
|
|
356
501
|
/**
|
|
357
502
|
* Снимок состояния для передачи клиенту / API
|
|
358
503
|
*/
|
|
359
|
-
getSnapshot() {
|
|
360
|
-
|
|
504
|
+
getSnapshot(sessionId = 'default') {
|
|
505
|
+
const sid = sessionId || 'default';
|
|
506
|
+
const goal = this.goals.get(sid);
|
|
507
|
+
if (!goal) {
|
|
361
508
|
return {
|
|
509
|
+
sessionId: sid,
|
|
362
510
|
hasActiveGoal: false,
|
|
363
511
|
state: GoalState.IDLE,
|
|
364
512
|
title: '',
|
|
@@ -377,29 +525,30 @@ export class GoalEngine {
|
|
|
377
525
|
};
|
|
378
526
|
}
|
|
379
527
|
|
|
380
|
-
const elapsed = this.getElapsedSeconds();
|
|
381
|
-
const milestones =
|
|
528
|
+
const elapsed = this.getElapsedSeconds(sid);
|
|
529
|
+
const milestones = goal.milestones;
|
|
382
530
|
const completedCount = milestones.filter((m) => m.status === MilestoneStatus.COMPLETED).length;
|
|
383
531
|
const progressPercent = milestones.length > 0 ? Math.round((completedCount / milestones.length) * 100) : 0;
|
|
384
532
|
|
|
385
533
|
return {
|
|
534
|
+
sessionId: sid,
|
|
386
535
|
hasActiveGoal: true,
|
|
387
|
-
id:
|
|
388
|
-
state:
|
|
389
|
-
title:
|
|
390
|
-
description:
|
|
391
|
-
startedAt:
|
|
392
|
-
pausedAt:
|
|
393
|
-
totalPausedDurationMs:
|
|
394
|
-
completedAt:
|
|
536
|
+
id: goal.id,
|
|
537
|
+
state: goal.state,
|
|
538
|
+
title: goal.title,
|
|
539
|
+
description: goal.description,
|
|
540
|
+
startedAt: goal.startedAt,
|
|
541
|
+
pausedAt: goal.pausedAt,
|
|
542
|
+
totalPausedDurationMs: goal.totalPausedDurationMs,
|
|
543
|
+
completedAt: goal.completedAt,
|
|
395
544
|
elapsedSeconds: elapsed,
|
|
396
545
|
formattedElapsed: formatElapsed(elapsed),
|
|
397
|
-
iterationsCount:
|
|
398
|
-
maxIterations:
|
|
546
|
+
iterationsCount: goal.iterationsCount,
|
|
547
|
+
maxIterations: goal.maxIterations,
|
|
399
548
|
milestones,
|
|
400
549
|
progressPercent,
|
|
401
|
-
logs:
|
|
402
|
-
resultSummary:
|
|
550
|
+
logs: goal.logs,
|
|
551
|
+
resultSummary: goal.resultSummary,
|
|
403
552
|
autoDrive: this.autoDrive,
|
|
404
553
|
enableSound: this.enableSound,
|
|
405
554
|
};
|
|
@@ -408,12 +557,14 @@ export class GoalEngine {
|
|
|
408
557
|
/**
|
|
409
558
|
* Формирование системного контекста для инжекта модели
|
|
410
559
|
*/
|
|
411
|
-
getStatePromptInjection() {
|
|
412
|
-
|
|
560
|
+
getStatePromptInjection(sessionId = 'default') {
|
|
561
|
+
const sid = sessionId || 'default';
|
|
562
|
+
const goal = this.goals.get(sid);
|
|
563
|
+
if (!goal || goal.state !== GoalState.RUNNING) {
|
|
413
564
|
return '';
|
|
414
565
|
}
|
|
415
566
|
|
|
416
|
-
const snapshot = this.getSnapshot();
|
|
567
|
+
const snapshot = this.getSnapshot(sid);
|
|
417
568
|
const hasMilestones = snapshot.milestones.length > 0;
|
|
418
569
|
const milestonesText = hasMilestones
|
|
419
570
|
? snapshot.milestones.map((m, i) => ` ${i + 1}. [${m.status.toUpperCase()}] ${m.title}${m.notes ? ` (${m.notes})` : ''}`).join('\n')
|
|
@@ -425,9 +576,9 @@ export class GoalEngine {
|
|
|
425
576
|
План работ:
|
|
426
577
|
${milestonesText}
|
|
427
578
|
|
|
428
|
-
Инструкции Goal Mode:
|
|
429
|
-
1. ${hasMilestones ? 'Выполняй текущий активный пункт плана работ.' : 'ТВОЙ ПЕРВЫЙ ШАГ: Немедленно вызови инструмент goal_set_milestones со списком пунктов плана
|
|
430
|
-
2. По мере выполнения каждого шага отмечай его статус через инструмент goal_update_progress (status: "in_progress" перед
|
|
431
|
-
3. Когда все пункты плана будут выполнены, вызови инструмент goal_finish с итоговым
|
|
579
|
+
Инструкции Goal Mode (СТРОГО ОБЯЗАТЕЛЬНЫ К ВЫПОЛНЕНИЮ):
|
|
580
|
+
1. ${hasMilestones ? 'Выполняй текущий активный пункт плана работ.' : 'ТВОЙ ПЕРВЫЙ ШАГ: Немедленно вызови инструмент goal_set_milestones со списком пунктов плана работ (3-7 конкретных шагов). Запрещено выполнять работу или завершать turn без вызова goal_set_milestones!'}
|
|
581
|
+
2. По мере выполнения каждого шага обязательно отмечай его статус через инструмент goal_update_progress (status: "in_progress" перед началом шага, status: "completed" по его завершении с кратким notes).
|
|
582
|
+
3. Когда все пункты плана будут выполнены, вызови инструмент goal_finish с подробным итоговым резюме достигнутых результатов.`;
|
|
432
583
|
}
|
|
433
584
|
}
|