@goodandready/dsh-goal 0.1.9 → 0.2.0
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 +13 -0
- package/{docs/README.ru.md → README.ru.md} +18 -1
- package/README.zh.md +4 -0
- package/docs/design/DESIGN.md +66 -0
- package/lib/client.js +358 -11
- package/lib/goal-engine.js +988 -780
- package/lib/index.js +290 -41
- package/package.json +8 -7
- package/docs/README.zh.md +0 -143
package/lib/index.js
CHANGED
|
@@ -43,6 +43,8 @@ export const Config = z.object({
|
|
|
43
43
|
autoDrive: z.boolean().default(true).description('Automatically continue the loop after each turn'),
|
|
44
44
|
enableSound: z.boolean().default(true).description('Play synthesized audio chime on goal completion or failure'),
|
|
45
45
|
showQuickLaunchButton: z.boolean().default(true).description('Show quick launch goal button above composer dock'),
|
|
46
|
+
consecutiveToolFailureLimit: z.number().default(3).description('Auto-pause goal if N consecutive turns encounter tool execution errors (0 to disable)'),
|
|
47
|
+
enableBrowserNotifications: z.boolean().default(true).description('Show desktop notifications on goal completion or failure'),
|
|
46
48
|
storagePath: z.string().default('').description('Custom filesystem path for persistent state storage'),
|
|
47
49
|
});
|
|
48
50
|
|
|
@@ -61,6 +63,8 @@ export function apply(ctx, config = {}) {
|
|
|
61
63
|
autoDrive: config?.autoDrive ?? true,
|
|
62
64
|
enableSound: config?.enableSound ?? true,
|
|
63
65
|
showQuickLaunchButton: config?.showQuickLaunchButton ?? true,
|
|
66
|
+
consecutiveToolFailureLimit: config?.consecutiveToolFailureLimit ?? 3,
|
|
67
|
+
enableBrowserNotifications: config?.enableBrowserNotifications ?? true,
|
|
64
68
|
storagePath: config?.storagePath,
|
|
65
69
|
};
|
|
66
70
|
|
|
@@ -74,6 +78,7 @@ export function apply(ctx, config = {}) {
|
|
|
74
78
|
autoDrive: currentSettings.autoDrive,
|
|
75
79
|
enableSound: currentSettings.enableSound,
|
|
76
80
|
showQuickLaunchButton: currentSettings.showQuickLaunchButton,
|
|
81
|
+
consecutiveToolFailureLimit: currentSettings.consecutiveToolFailureLimit,
|
|
77
82
|
storagePath,
|
|
78
83
|
});
|
|
79
84
|
|
|
@@ -222,6 +227,8 @@ export function apply(ctx, config = {}) {
|
|
|
222
227
|
autoDrive: typeof live.autoDrive === 'boolean' ? live.autoDrive : currentSettings.autoDrive,
|
|
223
228
|
enableSound: typeof live.enableSound === 'boolean' ? live.enableSound : currentSettings.enableSound,
|
|
224
229
|
showQuickLaunchButton: typeof live.showQuickLaunchButton === 'boolean' ? live.showQuickLaunchButton : currentSettings.showQuickLaunchButton,
|
|
230
|
+
consecutiveToolFailureLimit: typeof live.consecutiveToolFailureLimit === 'number' ? live.consecutiveToolFailureLimit : currentSettings.consecutiveToolFailureLimit,
|
|
231
|
+
enableBrowserNotifications: typeof live.enableBrowserNotifications === 'boolean' ? live.enableBrowserNotifications : currentSettings.enableBrowserNotifications,
|
|
225
232
|
storagePath: typeof live.storagePath === 'string' ? live.storagePath : currentSettings.storagePath,
|
|
226
233
|
};
|
|
227
234
|
}
|
|
@@ -249,6 +256,7 @@ export function apply(ctx, config = {}) {
|
|
|
249
256
|
autoDrive: live.autoDrive,
|
|
250
257
|
enableSound: live.enableSound,
|
|
251
258
|
showQuickLaunchButton: live.showQuickLaunchButton,
|
|
259
|
+
consecutiveToolFailureLimit: live.consecutiveToolFailureLimit,
|
|
252
260
|
});
|
|
253
261
|
};
|
|
254
262
|
|
|
@@ -309,9 +317,15 @@ export function apply(ctx, config = {}) {
|
|
|
309
317
|
ctx.inject(['systemPrompt'], (pctx) => {
|
|
310
318
|
try {
|
|
311
319
|
if (typeof pctx.systemPrompt?.section === 'function') {
|
|
320
|
+
// Clear conflicting core tool:goal prompt section if present
|
|
321
|
+
const globalSections = pctx.systemPrompt.layers?.global?.sections;
|
|
322
|
+
if (globalSections?.data instanceof Map && globalSections.data.has('tool:goal')) {
|
|
323
|
+
globalSections.data.delete('tool:goal');
|
|
324
|
+
}
|
|
325
|
+
|
|
312
326
|
const order = typeof pctx.systemPrompt.getSectionOrder === 'function'
|
|
313
|
-
? (pctx.systemPrompt.getSectionOrder('TOOL_GOAL') ||
|
|
314
|
-
:
|
|
327
|
+
? (pctx.systemPrompt.getSectionOrder('TOOL_GOAL') || 2400)
|
|
328
|
+
: 2400;
|
|
315
329
|
|
|
316
330
|
const unregisterSection = pctx.systemPrompt.section({
|
|
317
331
|
name: 'tool:dsh-goal',
|
|
@@ -335,8 +349,198 @@ export function apply(ctx, config = {}) {
|
|
|
335
349
|
ctx.inject(['tools'], (tctx) => {
|
|
336
350
|
if (!tctx.tools?.register) return;
|
|
337
351
|
|
|
338
|
-
//
|
|
339
|
-
|
|
352
|
+
// Helper: Safely replace or register tool in ToolRuntime
|
|
353
|
+
const safeRegister = (definition) => {
|
|
354
|
+
try {
|
|
355
|
+
const name = definition.name;
|
|
356
|
+
const globalTools = tctx.tools.layers?.global?.tools;
|
|
357
|
+
if (globalTools?.data instanceof Map && globalTools.data.has(name)) {
|
|
358
|
+
globalTools.data.delete(name);
|
|
359
|
+
}
|
|
360
|
+
const unregister = tctx.tools.register(definition);
|
|
361
|
+
if (typeof unregister === 'function') {
|
|
362
|
+
tctx.effect(() => () => unregister(), `dsh-goal: tool ${name}`);
|
|
363
|
+
}
|
|
364
|
+
} catch (err) {
|
|
365
|
+
console.warn(`[dsh-goal] Tool ${definition.name} registration skipped:`, err.message);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const JSON_OUTPUT = {
|
|
370
|
+
schema: { type: 'object', additionalProperties: true },
|
|
371
|
+
render: (_args, val) => [{ type: 'text', text: JSON.stringify(val) }],
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
function formatGoalValue(snap) {
|
|
375
|
+
if (!snap || !snap.hasActiveGoal) {
|
|
376
|
+
return { goal: null };
|
|
377
|
+
}
|
|
378
|
+
let phase = 'active';
|
|
379
|
+
if (snap.state === GoalState.PAUSED) phase = 'paused';
|
|
380
|
+
else if (snap.state === GoalState.COMPLETED) phase = 'complete';
|
|
381
|
+
|
|
382
|
+
const roundsStarted = Number.isInteger(snap.iterations) ? snap.iterations : 0;
|
|
383
|
+
const maxGoalRounds = Number.isInteger(snap.maxIterations) ? snap.maxIterations : 25;
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
goal: {
|
|
387
|
+
id: snap.id || 'goal-active',
|
|
388
|
+
revision: 1,
|
|
389
|
+
objective: snap.title || 'Goal',
|
|
390
|
+
phase,
|
|
391
|
+
roundsStarted,
|
|
392
|
+
maxGoalRounds,
|
|
393
|
+
},
|
|
394
|
+
activation: snap.state === GoalState.RUNNING ? 'armed' : 'disarmed',
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Совместимый инструмент 1: get_goal
|
|
399
|
+
const handleGetGoal = async (_args, toolCtx) => {
|
|
400
|
+
const sid = sessionIdOf(toolCtx, 'default');
|
|
401
|
+
const snap = engine.getSnapshot(sid);
|
|
402
|
+
return formatGoalValue(snap);
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
safeRegister({
|
|
406
|
+
name: 'get_goal',
|
|
407
|
+
description: 'Read the current same-session goal, including objective, phase, and round limits.',
|
|
408
|
+
parameters: { type: 'object', properties: {} },
|
|
409
|
+
output: JSON_OUTPUT,
|
|
410
|
+
execute: handleGetGoal,
|
|
411
|
+
handler: handleGetGoal,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Совместимый инструмент 2: create_goal
|
|
415
|
+
const handleCreateGoal = async (args, toolCtx) => {
|
|
416
|
+
const sid = sessionIdOf(toolCtx, 'default');
|
|
417
|
+
const maxIterations = Number(args.max_goal_rounds) || 25;
|
|
418
|
+
const snap = engine.startGoal(args.objective, { maxIterations }, sid);
|
|
419
|
+
return formatGoalValue(snap);
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
safeRegister({
|
|
423
|
+
name: 'create_goal',
|
|
424
|
+
description: 'Create one persisted same-session completion goal for long-running autonomous work.',
|
|
425
|
+
parameters: {
|
|
426
|
+
type: 'object',
|
|
427
|
+
properties: {
|
|
428
|
+
objective: {
|
|
429
|
+
type: 'string',
|
|
430
|
+
description: 'The concrete completion objective.',
|
|
431
|
+
},
|
|
432
|
+
max_goal_rounds: {
|
|
433
|
+
type: 'number',
|
|
434
|
+
description: 'Optional positive integer limit on automatic continuation rounds.',
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
required: ['objective'],
|
|
438
|
+
},
|
|
439
|
+
output: JSON_OUTPUT,
|
|
440
|
+
execute: handleCreateGoal,
|
|
441
|
+
handler: handleCreateGoal,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Совместимый инструмент 3: update_goal (перехватчик authority checks и роутер в GoalEngine)
|
|
445
|
+
const handleUpdateGoal = async (args, toolCtx) => {
|
|
446
|
+
const sid = sessionIdOf(toolCtx, 'default');
|
|
447
|
+
const action = args.action;
|
|
448
|
+
|
|
449
|
+
if (action === 'complete') {
|
|
450
|
+
const summary = args.blocked_reason || args.objective || 'Goal marked complete';
|
|
451
|
+
const snap = engine.completeGoal(summary, sid);
|
|
452
|
+
if (toolCtx?.deferContext) {
|
|
453
|
+
try {
|
|
454
|
+
toolCtx.deferContext({
|
|
455
|
+
type: 'text',
|
|
456
|
+
text: `<goal_complete>\nObjective: ${JSON.stringify(snap.title || summary)}\nThe goal is marked complete. Summarize what was accomplished for the user.\n</goal_complete>`,
|
|
457
|
+
});
|
|
458
|
+
} catch (_) {}
|
|
459
|
+
}
|
|
460
|
+
return formatGoalValue(snap);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (action === 'pause') {
|
|
464
|
+
const reason = args.blocked_reason || 'Paused by model';
|
|
465
|
+
const snap = engine.pause(reason, sid);
|
|
466
|
+
return formatGoalValue(snap);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (action === 'resume') {
|
|
470
|
+
const snap = engine.resume(sid);
|
|
471
|
+
resumeActiveAgent(undefined, sid);
|
|
472
|
+
return formatGoalValue(snap);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (action === 'edit') {
|
|
476
|
+
const snap = engine.getSnapshot(sid);
|
|
477
|
+
if (args.objective) snap.title = args.objective;
|
|
478
|
+
if (args.max_goal_rounds) snap.maxIterations = Number(args.max_goal_rounds);
|
|
479
|
+
engine.emit(sid, true);
|
|
480
|
+
return formatGoalValue(snap);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (action === 'blocked') {
|
|
484
|
+
const reason = args.blocked_reason || 'Goal blocked';
|
|
485
|
+
const snap = engine.pause('Blocked: ' + reason, sid);
|
|
486
|
+
if (toolCtx?.deferContext) {
|
|
487
|
+
try {
|
|
488
|
+
toolCtx.deferContext({
|
|
489
|
+
type: 'text',
|
|
490
|
+
text: `<goal_blocked>\nObjective: ${JSON.stringify(snap.title || 'Goal')}\nBlocked: ${JSON.stringify(reason)}\nExplain to the user what blocked progress.\n</goal_blocked>`,
|
|
491
|
+
});
|
|
492
|
+
} catch (_) {}
|
|
493
|
+
}
|
|
494
|
+
const res = formatGoalValue(snap);
|
|
495
|
+
if (res.goal) {
|
|
496
|
+
res.goal.phase = 'blocked';
|
|
497
|
+
res.goal.blockedReason = { code: 'model-reported', message: reason };
|
|
498
|
+
}
|
|
499
|
+
return res;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return formatGoalValue(engine.getSnapshot(sid));
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
safeRegister({
|
|
506
|
+
name: 'update_goal',
|
|
507
|
+
description: 'Update the active goal: complete, pause, resume, edit, or report blocked.',
|
|
508
|
+
parameters: {
|
|
509
|
+
type: 'object',
|
|
510
|
+
properties: {
|
|
511
|
+
goal_id: { type: 'string', description: 'Exact id returned by get_goal.' },
|
|
512
|
+
revision: { type: 'number', description: 'Exact positive revision returned by get_goal.' },
|
|
513
|
+
action: {
|
|
514
|
+
type: 'string',
|
|
515
|
+
enum: ['edit', 'pause', 'resume', 'complete', 'blocked'],
|
|
516
|
+
description: 'edit | pause | resume | complete | blocked',
|
|
517
|
+
},
|
|
518
|
+
objective: { type: 'string', description: 'Replacement objective; valid with action edit.' },
|
|
519
|
+
max_goal_rounds: { type: 'number', description: 'Replacement cap; valid with action edit.' },
|
|
520
|
+
blocked_reason: { type: 'string', description: 'Concrete blocking condition; required with action blocked.' },
|
|
521
|
+
},
|
|
522
|
+
required: ['action'],
|
|
523
|
+
},
|
|
524
|
+
output: JSON_OUTPUT,
|
|
525
|
+
execute: handleUpdateGoal,
|
|
526
|
+
handler: handleUpdateGoal,
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// Инструмент 4: Декомпозиция цели на вехи
|
|
530
|
+
const handleSetMilestones = async ({ milestones }, toolCtx) => {
|
|
531
|
+
const sid = sessionIdOf(toolCtx, 'default');
|
|
532
|
+
const snap = engine.getSnapshot(sid);
|
|
533
|
+
if (!snap.hasActiveGoal) {
|
|
534
|
+
return { error: 'No active goal currently set. Start a goal first.' };
|
|
535
|
+
}
|
|
536
|
+
engine.addMilestones(milestones, true, sid);
|
|
537
|
+
return {
|
|
538
|
+
success: true,
|
|
539
|
+
milestones: engine.getSnapshot(sid).milestones,
|
|
540
|
+
};
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
safeRegister({
|
|
340
544
|
name: 'goal_set_milestones',
|
|
341
545
|
description: 'Break down the current active goal into a sequence of concrete milestones/sub-tasks.',
|
|
342
546
|
parameters: {
|
|
@@ -350,22 +554,25 @@ export function apply(ctx, config = {}) {
|
|
|
350
554
|
},
|
|
351
555
|
required: ['milestones'],
|
|
352
556
|
},
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (!snap.hasActiveGoal) {
|
|
357
|
-
return { error: 'No active goal currently set. Start a goal first.' };
|
|
358
|
-
}
|
|
359
|
-
engine.addMilestones(milestones, true, sid);
|
|
360
|
-
return {
|
|
361
|
-
success: true,
|
|
362
|
-
milestones: engine.getSnapshot(sid).milestones,
|
|
363
|
-
};
|
|
364
|
-
},
|
|
557
|
+
output: JSON_OUTPUT,
|
|
558
|
+
execute: handleSetMilestones,
|
|
559
|
+
handler: handleSetMilestones,
|
|
365
560
|
});
|
|
366
561
|
|
|
367
|
-
// Инструмент
|
|
368
|
-
|
|
562
|
+
// Инструмент 5: Обновление статуса вехи
|
|
563
|
+
const handleUpdateProgress = async ({ milestone_id, status, notes }, toolCtx) => {
|
|
564
|
+
const sid = sessionIdOf(toolCtx, 'default');
|
|
565
|
+
const ok = engine.updateMilestone(milestone_id, status, notes, sid);
|
|
566
|
+
if (!ok) {
|
|
567
|
+
return { error: `Milestone ${milestone_id} not found or no active goal.` };
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
success: true,
|
|
571
|
+
snapshot: engine.getSnapshot(sid),
|
|
572
|
+
};
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
safeRegister({
|
|
369
576
|
name: 'goal_update_progress',
|
|
370
577
|
description: 'Update the status of a specific goal milestone and optionally log progress notes.',
|
|
371
578
|
parameters: {
|
|
@@ -387,21 +594,23 @@ export function apply(ctx, config = {}) {
|
|
|
387
594
|
},
|
|
388
595
|
required: ['milestone_id', 'status'],
|
|
389
596
|
},
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
if (!ok) {
|
|
394
|
-
return { error: `Milestone ${milestone_id} not found or no active goal.` };
|
|
395
|
-
}
|
|
396
|
-
return {
|
|
397
|
-
success: true,
|
|
398
|
-
snapshot: engine.getSnapshot(sid),
|
|
399
|
-
};
|
|
400
|
-
},
|
|
597
|
+
output: JSON_OUTPUT,
|
|
598
|
+
execute: handleUpdateProgress,
|
|
599
|
+
handler: handleUpdateProgress,
|
|
401
600
|
});
|
|
402
601
|
|
|
403
|
-
// Инструмент
|
|
404
|
-
|
|
602
|
+
// Инструмент 6: Успешное завершение цели
|
|
603
|
+
const handleGoalFinish = async ({ summary }, toolCtx) => {
|
|
604
|
+
const sid = sessionIdOf(toolCtx, 'default');
|
|
605
|
+
const snap = engine.completeGoal(summary, sid);
|
|
606
|
+
return {
|
|
607
|
+
success: true,
|
|
608
|
+
completed: true,
|
|
609
|
+
summary,
|
|
610
|
+
};
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
safeRegister({
|
|
405
614
|
name: 'goal_finish',
|
|
406
615
|
description: 'Conclude the active goal successfully with a final summary and achievements.',
|
|
407
616
|
parameters: {
|
|
@@ -414,15 +623,9 @@ export function apply(ctx, config = {}) {
|
|
|
414
623
|
},
|
|
415
624
|
required: ['summary'],
|
|
416
625
|
},
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
return {
|
|
421
|
-
success: true,
|
|
422
|
-
completed: true,
|
|
423
|
-
summary,
|
|
424
|
-
};
|
|
425
|
-
},
|
|
626
|
+
output: JSON_OUTPUT,
|
|
627
|
+
execute: handleGoalFinish,
|
|
628
|
+
handler: handleGoalFinish,
|
|
426
629
|
});
|
|
427
630
|
});
|
|
428
631
|
|
|
@@ -553,6 +756,20 @@ export function apply(ctx, config = {}) {
|
|
|
553
756
|
resumeActiveAgent(startPrompt, sid);
|
|
554
757
|
break;
|
|
555
758
|
}
|
|
759
|
+
case 'nudge': {
|
|
760
|
+
const text = (typeof data.text === 'string' ? data.text : (data.notes || data.nudge || '')).trim();
|
|
761
|
+
if (!text) {
|
|
762
|
+
res.statusCode = 400;
|
|
763
|
+
return res.end(JSON.stringify({ error: 'Nudge text cannot be empty' }));
|
|
764
|
+
}
|
|
765
|
+
result = engine.nudge(text, sid);
|
|
766
|
+
if (data.resume) {
|
|
767
|
+
engine.resume(sid);
|
|
768
|
+
const prompt = engine.getStatePromptInjection(sid);
|
|
769
|
+
resumeActiveAgent(prompt, sid);
|
|
770
|
+
}
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
556
773
|
case 'pause':
|
|
557
774
|
result = engine.pause(reason || 'Пауза по кнопке интерфейса', sid);
|
|
558
775
|
stopRunningAgents(sid);
|
|
@@ -639,6 +856,29 @@ export function apply(ctx, config = {}) {
|
|
|
639
856
|
|
|
640
857
|
const snap = engine.getSnapshot(sid);
|
|
641
858
|
if (snap.hasActiveGoal && snap.state === GoalState.RUNNING) {
|
|
859
|
+
// Tool-Failure Breaker: отслеживание повторяющихся ошибок инструментов
|
|
860
|
+
const steps = turn?.steps || turn?.turn?.steps || [];
|
|
861
|
+
const hasStepError = Array.isArray(steps) && steps.some((s) => {
|
|
862
|
+
return s?.status === 'error' || s?.error || s?.toolResult?.isError;
|
|
863
|
+
});
|
|
864
|
+
const hasTurnError = Boolean(turn?.error || turn?.reason?.kind === 'error');
|
|
865
|
+
const isToolFailure = hasStepError || hasTurnError;
|
|
866
|
+
|
|
867
|
+
if (isToolFailure) {
|
|
868
|
+
const failCount = engine.incrementToolFailureCount(sid);
|
|
869
|
+
const limit = liveConfig.consecutiveToolFailureLimit;
|
|
870
|
+
if (limit > 0 && failCount >= limit) {
|
|
871
|
+
const lang = snap.lang || (snap.title ? detectLanguage(snap.title) : 'en');
|
|
872
|
+
const pauseReason = lang === 'ru'
|
|
873
|
+
? `Повторяющаяся ошибка инструментов (${failCount} подряд) — цель приостановлена для защиты от зацикливания`
|
|
874
|
+
: `Repeated tool failure (${failCount} consecutive) — goal paused to prevent token burn`;
|
|
875
|
+
engine.pause(pauseReason, sid);
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
} else {
|
|
879
|
+
engine.resetToolFailureCount(sid);
|
|
880
|
+
}
|
|
881
|
+
|
|
642
882
|
// Проверяем причину завершения хода
|
|
643
883
|
if (turn?.reason?.kind === 'aborted' || turn?.reason?.kind === 'error' || turn?.error) {
|
|
644
884
|
return;
|
|
@@ -663,9 +903,18 @@ export function apply(ctx, config = {}) {
|
|
|
663
903
|
const currentSnap = engine.getSnapshot(sid);
|
|
664
904
|
if (currentSnap.hasActiveGoal && currentSnap.state === GoalState.RUNNING) {
|
|
665
905
|
const currentLang = currentSnap.lang || (currentSnap.title ? detectLanguage(currentSnap.title) : 'en');
|
|
666
|
-
|
|
906
|
+
let promptText = currentLang === 'ru'
|
|
667
907
|
? 'Продолжай автономное выполнение цели согласно плану работ. Отмечай каждый выполненный шаг через goal_update_progress, а по завершении всех задач вызови goal_finish.'
|
|
668
908
|
: 'Continue autonomous goal execution according to the work plan. Mark each completed step via goal_update_progress, and when all tasks are complete, call goal_finish.';
|
|
909
|
+
|
|
910
|
+
if (currentSnap.pendingNudge) {
|
|
911
|
+
const userNudge = engine.consumePendingNudge(sid);
|
|
912
|
+
if (userNudge) {
|
|
913
|
+
promptText = (currentLang === 'ru'
|
|
914
|
+
? `🚨 СРОЧНОЕ УТОЧНЕНИЕ / НАПРАВЛЕНИЕ ОТ ПОЛЬЗОВАТЕЛЯ:\n"${userNudge}"\nСкорректируй выполнение с учётом этого замечания.\n\n`
|
|
915
|
+
: `🚨 URGENT USER CLARIFICATION / STEERING:\n"${userNudge}"\nAdjust execution adhering to this feedback.\n\n`) + promptText;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
669
918
|
const promptMsg = createGoalUserMessage(promptText);
|
|
670
919
|
let target = sessionAgents.get(sid);
|
|
671
920
|
if (!target && lastActiveAgent && typeof lastActiveAgent.followup === 'function') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-goal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Autonomous Goal Execution & Multi-Turn Task Tracking Engine with Sticky Header for DeepSeek Harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -14,10 +14,14 @@
|
|
|
14
14
|
"lib/",
|
|
15
15
|
"cordis.patch.yml",
|
|
16
16
|
"README.md",
|
|
17
|
-
"
|
|
17
|
+
"README.ru.md",
|
|
18
|
+
"README.zh.md",
|
|
18
19
|
"LICENSE",
|
|
19
|
-
"
|
|
20
|
+
"docs/design/DESIGN.md"
|
|
20
21
|
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"test": "node --test test/*.test.mjs"
|
|
24
|
+
},
|
|
21
25
|
"keywords": [
|
|
22
26
|
"dsh",
|
|
23
27
|
"dsh-plugin",
|
|
@@ -55,8 +59,5 @@
|
|
|
55
59
|
},
|
|
56
60
|
"dependencies": {
|
|
57
61
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
58
|
-
},
|
|
59
|
-
"scripts": {
|
|
60
|
-
"test": "node --test test/*.test.mjs"
|
|
61
62
|
}
|
|
62
|
-
}
|
|
63
|
+
}
|
package/docs/README.zh.md
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
# 📦 @goodandready/dsh-goal
|
|
2
|
-
|
|
3
|
-
<div align="center">
|
|
4
|
-
|
|
5
|
-
<h3>面向 DeepSeek Harness 的自主目标执行、任务里程碑分解与顶部常驻状态条引擎</h3>
|
|
6
|
-
|
|
7
|
-
<p align="center">
|
|
8
|
-
<a href="https://www.npmjs.com/package/@goodandready/dsh-goal"><img src="https://img.shields.io/npm/v/@goodandready/dsh-goal.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
|
|
9
|
-
<a href="../LICENSE"><img src="https://img.shields.io/github/license/GooDAnDReaDY/dsh-goal.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
|
|
10
|
-
<a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
|
|
11
|
-
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
|
|
12
|
-
</p>
|
|
13
|
-
|
|
14
|
-
<!-- 作者所有项目展示页面链接 -->
|
|
15
|
-
<p align="center">
|
|
16
|
-
<a href="https://goodandready.app/"><img src="https://img.shields.io/badge/作者所有开源项目-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="所有项目"></a>
|
|
17
|
-
</p>
|
|
18
|
-
|
|
19
|
-
<p align="center">
|
|
20
|
-
<a href="../README.md"><b>🇬🇧 English</b></a> •
|
|
21
|
-
<a href="README.ru.md"><b>🇷🇺 Русский</b></a> •
|
|
22
|
-
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
23
|
-
</p>
|
|
24
|
-
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## ⚡ 概述与核心解决痛点
|
|
30
|
-
|
|
31
|
-
复杂的软件工程任务往往需要多步骤的自主循环:将宏观目标分解为里程碑子任务、多轮自动连续推进而无需用户反复手动下发指令,以及对执行过程保持清晰的可视化监控。
|
|
32
|
-
|
|
33
|
-
**`@goodandready/dsh-goal`** 通过 `/goal` 指令为 DeepSeek Harness 带来原生的目标自主执行系统:
|
|
34
|
-
* 🎯 **顶部常驻状态条**:固定在聊天窗口顶部的状态横幅,显示实时运行计时器(`• 2s`, `• 1m 45s`)、当前目标标题与控制按钮。
|
|
35
|
-
* ⏸️ **自主循环控制 (Play / Pause / Cancel)**:可随时暂停智能体自主多轮循环,或根据需要一键恢复。
|
|
36
|
-
* 📋 **里程碑任务抽屉**:交互式子任务清单,展示完成百分比进度条与执行日志。
|
|
37
|
-
* 🤖 **智能体原生工具**:提供 `goal_set_milestones`, `goal_update_progress`, `goal_finish` 工具。
|
|
38
|
-
* 🛡️ **安全防护机制**:支持配置 `maxIterations` 最大循环轮次,杜绝死循环消耗。
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## 🏛️ 架构设计
|
|
43
|
-
|
|
44
|
-
```mermaid
|
|
45
|
-
graph TD
|
|
46
|
-
subgraph Input ["用户交互与输入"]
|
|
47
|
-
Cmd["聊天斜杠指令: /goal <目标描述>"]
|
|
48
|
-
API["REST API 路由: POST /dsh-goal/action"]
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
subgraph GoalEngine ["目标生命周期引擎 (lib/index.js)"]
|
|
52
|
-
State["状态机 (IDLE, RUNNING, PAUSED, COMPLETED)"]
|
|
53
|
-
Milestones["里程碑分解与进度追踪器"]
|
|
54
|
-
Disk["状态持久化存储 (~/.dsh/goal-state.json)"]
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
subgraph AgentLoop ["智能体自主执行驱动"]
|
|
58
|
-
ToolSet["goal_set_milestones"]
|
|
59
|
-
ToolProgress["goal_update_progress"]
|
|
60
|
-
ToolFinish["goal_finish"]
|
|
61
|
-
LimitGuard{"maxIterations 熔断防护"}
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
subgraph UI ["DSH Web 前端界面"]
|
|
65
|
-
Banner["顶部常驻状态横幅"]
|
|
66
|
-
Timer["实时执行计时器"]
|
|
67
|
-
Drawer["里程碑清单详情模态框"]
|
|
68
|
-
Settings["设置面板卡片 (Schemastery)"]
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
Cmd --> GoalEngine
|
|
72
|
-
API --> GoalEngine
|
|
73
|
-
GoalEngine --> State
|
|
74
|
-
State --> Disk
|
|
75
|
-
State --> Banner
|
|
76
|
-
State --> Drawer
|
|
77
|
-
GoalEngine --> AgentLoop
|
|
78
|
-
AgentLoop --> LimitGuard
|
|
79
|
-
ToolSet --> GoalEngine
|
|
80
|
-
ToolProgress --> GoalEngine
|
|
81
|
-
ToolFinish --> GoalEngine
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## 📦 快速安装
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
dsh plugin --profile web add @goodandready/dsh-goal
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
重启 DeepSeek Harness 实例并刷新浏览器页面。
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## 💬 快速上手
|
|
97
|
-
|
|
98
|
-
在聊天框中直接启动自主目标:
|
|
99
|
-
|
|
100
|
-
```text
|
|
101
|
-
/goal 重构身份验证中间件并编写端到端集成测试
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
或通过 REST 接口调用:
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
curl -X POST http://localhost:3080/dsh-goal/action \
|
|
108
|
-
-H "Content-Type: application/json" \
|
|
109
|
-
-d '{"action":"start","title":"优化数据库查询性能"}'
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
## ⚙️ 配置参数参考 (`settings.yaml`)
|
|
115
|
-
|
|
116
|
-
```yaml
|
|
117
|
-
dsh-goal:
|
|
118
|
-
maxIterations: 25
|
|
119
|
-
autoDrive: true
|
|
120
|
-
enableSound: true
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
| 参数项 | 类型 | 默认值 | 说明 |
|
|
124
|
-
|:---|:---|:---|:---|
|
|
125
|
-
| `maxIterations` | `number` | `25` | 安全熔断阈值:单目标允许的最大自主轮次 |
|
|
126
|
-
| `autoDrive` | `boolean` | `true` | 是否在轮次结束后自动驱动智能体继续推进 |
|
|
127
|
-
| `enableSound` | `boolean` | `true` | 目标完成时是否播放提示音效 |
|
|
128
|
-
|
|
129
|
-
---
|
|
130
|
-
|
|
131
|
-
## 🧪 自动化测试
|
|
132
|
-
|
|
133
|
-
运行自动化测试套件:
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
npm test
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
---
|
|
140
|
-
|
|
141
|
-
## 📄 许可证
|
|
142
|
-
|
|
143
|
-
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|