@goodandready/dsh-goal 0.2.31 → 0.2.33
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/client.js +333 -1
- package/lib/engine-compaction.js +126 -0
- package/lib/engine-cost-meter.js +161 -0
- package/lib/engine-prompt.js +59 -5
- package/lib/engine-release-notes.js +132 -0
- package/lib/engine-subgoals.js +207 -0
- package/lib/goal-engine-constants.js +30 -0
- package/lib/goal-engine.js +45 -44
- package/lib/index.js +19 -8
- package/lib/routes.js +51 -0
- package/lib/tools.js +69 -0
- package/package.json +1 -1
package/lib/goal-engine.js
CHANGED
|
@@ -13,8 +13,12 @@ import { parseIssueRef, parseIssueChecklist, syncMilestoneWithIssue } from './en
|
|
|
13
13
|
import { generateRetrospectiveData } from './engine-retrospective.js';
|
|
14
14
|
import { generatePreplanDraft } from './engine-templates.js';
|
|
15
15
|
import { pruneInactiveSessions, addNudgeToGoal, consumeGoalNudge, extendGoalBudget, setGoalMode, pauseGoal, resumeGoal, cancelGoal } from './engine-sessions.js';
|
|
16
|
+
import { compactMilestone, estimateCompactionSavings } from './engine-compaction.js';
|
|
17
|
+
import { spawnSubGoal, completeSubGoal, syncSubGoalToParent, getSubGoalBreadcrumbs, getSubGoalsList } from './engine-subgoals.js';
|
|
18
|
+
import { updateGoalCostUsage, processCostBudget, calculateCost, formatCurrency } from './engine-cost-meter.js';
|
|
19
|
+
import { generateReleaseNotes, dispatchGoalTelegramNotification } from './engine-release-notes.js';
|
|
16
20
|
|
|
17
|
-
export { GoalMode, GoalState, MilestoneStatus, formatElapsed, formatETA, detectLanguage, sessionIdOf, matchMilestone, parseIssueChecklist, getGitCurrentCommit, exportReportMarkdown, exportReportGitHubPR, createMilestoneCheckpoint, rollbackToCheckpoint, saveGoalArtifact };
|
|
21
|
+
export { GoalMode, GoalState, MilestoneStatus, formatElapsed, formatETA, detectLanguage, sessionIdOf, matchMilestone, parseIssueChecklist, getGitCurrentCommit, exportReportMarkdown, exportReportGitHubPR, createMilestoneCheckpoint, rollbackToCheckpoint, saveGoalArtifact, compactMilestone, estimateCompactionSavings, spawnSubGoal, completeSubGoal, getSubGoalBreadcrumbs, getSubGoalsList, calculateCost, formatCurrency, generateReleaseNotes, dispatchGoalTelegramNotification };
|
|
18
22
|
|
|
19
23
|
/**
|
|
20
24
|
* Isolated Goal Management Engine for DeepSeek Harness
|
|
@@ -34,8 +38,8 @@ export class GoalEngine {
|
|
|
34
38
|
autoBranchOnGoalStart: options.autoBranchOnGoalStart ?? true, enablePostGoalRetrospective: options.enablePostGoalRetrospective ?? true,
|
|
35
39
|
autoScaleBudgetNearCompletion: options.autoScaleBudgetNearCompletion ?? false, enableTemplatesDrawer: options.enableTemplatesDrawer ?? true,
|
|
36
40
|
enableMilestoneDependencies: options.enableMilestoneDependencies ?? true, soundScheme: options.soundScheme ?? 'default', enableKanbanSync: options.enableKanbanSync ?? true, ctx: options.ctx || null, kanbanService: options.kanbanService || null, kanbanStore: options.kanbanStore || null,
|
|
37
|
-
enableVoiceAnnouncements: options.enableVoiceAnnouncements ?? false, approvalGate: options.approvalGate || null,
|
|
38
|
-
checkApproval: options.checkApproval || null, maxSessions: options.maxSessions ?? 100,
|
|
41
|
+
enableVoiceAnnouncements: options.enableVoiceAnnouncements ?? false, enableMilestoneCompaction: options.enableMilestoneCompaction ?? true, approvalGate: options.approvalGate || null,
|
|
42
|
+
checkApproval: options.checkApproval || null, maxSessions: options.maxSessions ?? 100, maxCostBudget: options.maxCostBudget ?? 0, costWarningThreshold: options.costWarningThreshold ?? 80, currency: options.currency || 'USD', enableTelegramNotifications: options.enableTelegramNotifications ?? false, activeModel: options.activeModel || '', customRates: options.customRates || {},
|
|
39
43
|
});
|
|
40
44
|
this.goals = new Map();
|
|
41
45
|
this.listeners = new Set();
|
|
@@ -58,10 +62,7 @@ export class GoalEngine {
|
|
|
58
62
|
flushSync() { this.store.flushSync(); }
|
|
59
63
|
saveStateToDisk() { this.flushSync(); }
|
|
60
64
|
|
|
61
|
-
subscribe(callback) {
|
|
62
|
-
this.listeners.add(callback);
|
|
63
|
-
return () => this.listeners.delete(callback);
|
|
64
|
-
}
|
|
65
|
+
subscribe(callback) { this.listeners.add(callback); return () => this.listeners.delete(callback); }
|
|
65
66
|
|
|
66
67
|
emit(sessionId = 'default', immediate = false) {
|
|
67
68
|
this.scheduleSave(immediate);
|
|
@@ -76,21 +77,9 @@ export class GoalEngine {
|
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
recordProgress(sessionId = 'default') {
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
incrementStallCount(sessionId = 'default') {
|
|
84
|
-
const sid = sessionId || 'default';
|
|
85
|
-
const current = this.stallCounters.get(sid) || 0;
|
|
86
|
-
const next = current + 1;
|
|
87
|
-
this.stallCounters.set(sid, next);
|
|
88
|
-
return next;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
getStallCount(sessionId = 'default') {
|
|
92
|
-
return this.stallCounters.get(sessionId || 'default') || 0;
|
|
93
|
-
}
|
|
80
|
+
recordProgress(sessionId = 'default') { this.stallCounters.set(sessionId || 'default', 0); }
|
|
81
|
+
incrementStallCount(sessionId = 'default') { const sid = sessionId || 'default'; const n = (this.stallCounters.get(sid) || 0) + 1; this.stallCounters.set(sid, n); return n; }
|
|
82
|
+
getStallCount(sessionId = 'default') { return this.stallCounters.get(sessionId || 'default') || 0; }
|
|
94
83
|
|
|
95
84
|
updateConfig(config = {}) {
|
|
96
85
|
const rawMax = config.defaultMaxIterations ?? config.maxIterations;
|
|
@@ -104,12 +93,17 @@ export class GoalEngine {
|
|
|
104
93
|
}
|
|
105
94
|
}
|
|
106
95
|
}
|
|
107
|
-
const boolKeys = ['autoDrive', 'enableSound', 'showQuickLaunchButton', 'autoCheckpointOnMilestone', 'enableLiveActivityFeed', 'enablePreplanning', 'autoBranchOnGoalStart', 'enablePostGoalRetrospective', 'autoScaleBudgetNearCompletion', 'enableTemplatesDrawer', 'enableMilestoneDependencies', 'enableVoiceAnnouncements'];
|
|
96
|
+
const boolKeys = ['autoDrive', 'enableSound', 'showQuickLaunchButton', 'autoCheckpointOnMilestone', 'enableLiveActivityFeed', 'enablePreplanning', 'autoBranchOnGoalStart', 'enablePostGoalRetrospective', 'autoScaleBudgetNearCompletion', 'enableTemplatesDrawer', 'enableMilestoneDependencies', 'enableVoiceAnnouncements', 'enableMilestoneCompaction'];
|
|
108
97
|
for (const k of boolKeys) if (typeof config[k] === 'boolean') this[k] = config[k];
|
|
109
98
|
if (typeof config.consecutiveToolFailureLimit === 'number') this.consecutiveToolFailureLimit = Math.max(0, config.consecutiveToolFailureLimit);
|
|
110
99
|
if (typeof config.maxTokenBudget === 'number') this.maxTokenBudget = Math.max(0, config.maxTokenBudget);
|
|
111
100
|
if (typeof config.budgetWarningThreshold === 'number') this.budgetWarningThreshold = Math.max(1, Math.min(100, config.budgetWarningThreshold));
|
|
112
101
|
if (typeof config.soundScheme === 'string') this.soundScheme = config.soundScheme;
|
|
102
|
+
if (typeof config.maxCostBudget === 'number') this.maxCostBudget = Math.max(0, config.maxCostBudget);
|
|
103
|
+
if (typeof config.costWarningThreshold === 'number') this.costWarningThreshold = Math.max(1, Math.min(100, config.costWarningThreshold));
|
|
104
|
+
if (typeof config.enableTelegramNotifications === 'boolean') this.enableTelegramNotifications = config.enableTelegramNotifications;
|
|
105
|
+
if (typeof config.currency === 'string') this.currency = config.currency;
|
|
106
|
+
if (typeof config.activeModel === 'string') this.activeModel = config.activeModel;
|
|
113
107
|
this.emit();
|
|
114
108
|
}
|
|
115
109
|
|
|
@@ -208,7 +202,17 @@ export class GoalEngine {
|
|
|
208
202
|
issueRef: parseIssueRef(cleanTitle),
|
|
209
203
|
operatorQuery: null,
|
|
210
204
|
waitingForOperator: false,
|
|
205
|
+
parentGoalId: options.parentGoalId || null,
|
|
206
|
+
parentSessionId: options.parentSessionId || null,
|
|
207
|
+
isSubGoal: Boolean(options.parentGoalId),
|
|
208
|
+
subGoals: [],
|
|
211
209
|
tokensUsage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
210
|
+
maxCostBudget: options.maxCostBudget ?? this.maxCostBudget ?? 0,
|
|
211
|
+
currency: options.currency || this.currency || 'USD',
|
|
212
|
+
costUsage: { promptCost: 0, completionCost: 0, totalCost: 0, formattedCost: '.00', currency: options.currency || this.currency || 'USD' },
|
|
213
|
+
costWarningTriggered: false,
|
|
214
|
+
costLimitExceeded: false,
|
|
215
|
+
telegramNotificationsSent: [],
|
|
212
216
|
logs: [{
|
|
213
217
|
timestamp: now,
|
|
214
218
|
type: 'info',
|
|
@@ -305,16 +309,15 @@ export class GoalEngine {
|
|
|
305
309
|
m.status = MilestoneStatus.COMPLETED;
|
|
306
310
|
}
|
|
307
311
|
}
|
|
312
|
+
if (goal.parentSessionId) syncSubGoalToParent(this, sid, summary);
|
|
313
|
+
if (this.enableTelegramNotifications) { dispatchGoalTelegramNotification(goal, { engine: this, ctx: this.ctx }).catch(() => {}); }
|
|
308
314
|
this.stallCounters.delete(sid);
|
|
309
315
|
this.toolFailureCounters.delete(sid);
|
|
310
316
|
this.emit(sid, true);
|
|
311
317
|
return this.getSnapshot(sid);
|
|
312
318
|
}
|
|
313
319
|
|
|
314
|
-
setMilestones(milestonesList, sessionId = 'default') {
|
|
315
|
-
this.addMilestones(milestonesList, true, sessionId);
|
|
316
|
-
return this.getSnapshot(sessionId);
|
|
317
|
-
}
|
|
320
|
+
setMilestones(milestonesList, sessionId = 'default') { this.addMilestones(milestonesList, true, sessionId); return this.getSnapshot(sessionId); }
|
|
318
321
|
|
|
319
322
|
addMilestones(milestonesList, shouldEmit = true, sessionId = 'default') {
|
|
320
323
|
const sid = typeof shouldEmit === 'string' ? shouldEmit : (sessionId || 'default');
|
|
@@ -365,6 +368,7 @@ export class GoalEngine {
|
|
|
365
368
|
if (this.enableKanbanSync) syncMilestoneStatusToKanban(target, { goal, ctx: this.ctx, store: this.kanbanStore, kanbanService: this.kanbanService }).catch(() => {});
|
|
366
369
|
|
|
367
370
|
if (target.status === MilestoneStatus.COMPLETED && prevStatus !== MilestoneStatus.COMPLETED) {
|
|
371
|
+
if (this.enableMilestoneCompaction !== false) compactMilestone(target, { goal, cwd: goal.cwd });
|
|
368
372
|
if (this.autoCheckpointOnMilestone) {
|
|
369
373
|
const commitHash = createMilestoneCheckpoint(target, sid);
|
|
370
374
|
if (commitHash) {
|
|
@@ -439,9 +443,7 @@ export class GoalEngine {
|
|
|
439
443
|
return true;
|
|
440
444
|
}
|
|
441
445
|
|
|
442
|
-
getElapsedSeconds(sessionId = 'default') {
|
|
443
|
-
return calculateElapsedSeconds(this.goals.get(sessionId || 'default'));
|
|
444
|
-
}
|
|
446
|
+
getElapsedSeconds(sessionId = 'default') { return calculateElapsedSeconds(this.goals.get(sessionId || 'default')); }
|
|
445
447
|
|
|
446
448
|
addTokenUsage(usage, sessionId = 'default') {
|
|
447
449
|
if (!usage || typeof usage !== 'object') return;
|
|
@@ -458,32 +460,24 @@ export class GoalEngine {
|
|
|
458
460
|
goal.tokensUsage.completionTokens += completion;
|
|
459
461
|
goal.tokensUsage.totalTokens += total;
|
|
460
462
|
|
|
463
|
+
updateGoalCostUsage(goal, { promptTokens: prompt, completionTokens: completion, totalTokens: total, modelName: usage.modelName || usage.model || this.activeModel || '' }, this);
|
|
464
|
+
const costCheck = processCostBudget(goal, { maxCostBudget: goal.maxCostBudget ?? this.maxCostBudget, costWarningThreshold: this.costWarningThreshold, GoalState });
|
|
461
465
|
const budgetCheck = processTokenBudget(goal, {
|
|
462
466
|
maxTokenBudget: this.maxTokenBudget,
|
|
463
467
|
budgetWarningThreshold: this.budgetWarningThreshold,
|
|
464
468
|
autoScaleBudgetNearCompletion: this.autoScaleBudgetNearCompletion,
|
|
465
469
|
GoalState,
|
|
466
470
|
});
|
|
467
|
-
if (budgetCheck.paused) {
|
|
471
|
+
if (budgetCheck.paused || costCheck.paused) {
|
|
468
472
|
this.emit(sid, true);
|
|
469
473
|
return;
|
|
470
474
|
}
|
|
471
475
|
this.emit(sid);
|
|
472
476
|
}
|
|
473
477
|
|
|
474
|
-
getEstimatedRemainingSeconds(sessionId = 'default') {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
getSnapshot(sessionId = 'default') {
|
|
480
|
-
const sid = sessionId || 'default';
|
|
481
|
-
return buildSnapshot(this.goals.get(sid), sid, this);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
getGoalSnapshot(sessionId = 'default') {
|
|
485
|
-
return this.getSnapshot(sessionId);
|
|
486
|
-
}
|
|
478
|
+
getEstimatedRemainingSeconds(sessionId = 'default') { const sid = sessionId || 'default'; return calculateRemainingSeconds(this.goals.get(sid), this.getElapsedSeconds(sid)); }
|
|
479
|
+
getSnapshot(sessionId = 'default') { const sid = sessionId || 'default'; return buildSnapshot(this.goals.get(sid), sid, this); }
|
|
480
|
+
getGoalSnapshot(sessionId = 'default') { return this.getSnapshot(sessionId); }
|
|
487
481
|
|
|
488
482
|
getStatePromptInjection(sessionId = 'default') {
|
|
489
483
|
const sid = sessionId || 'default';
|
|
@@ -591,4 +585,11 @@ export class GoalEngine {
|
|
|
591
585
|
const goal = this.goals.get(sessionId || 'default');
|
|
592
586
|
return Boolean(goal && (goal.mode === GoalMode.PLAN || goal.state === GoalState.PLANNING));
|
|
593
587
|
}
|
|
588
|
+
spawnSubGoal(options = {}, sessionId = 'default') { return spawnSubGoal(this, sessionId, options); }
|
|
589
|
+
completeSubGoal(sessionId, summary = '') { return completeSubGoal(this, sessionId, summary); }
|
|
590
|
+
getSubGoals(sessionId = 'default') { return getSubGoalsList(this, sessionId); }
|
|
591
|
+
getSubGoalBreadcrumbs(sessionId = 'default') { return getSubGoalBreadcrumbs(this, sessionId); }
|
|
592
|
+
getCompactionSavings(sessionId = 'default') { const g = this.goals.get(sessionId || 'default'); return estimateCompactionSavings(g?.milestones || [], g?.lang || 'en'); }
|
|
593
|
+
exportReleaseNotes(sessionId = 'default', options = {}) { return generateReleaseNotes(this.goals.get(sessionId || 'default'), options); }
|
|
594
|
+
getCostUsage(sessionId = 'default') { return this.goals.get(sessionId || 'default')?.costUsage || null; }
|
|
594
595
|
}
|
package/lib/index.js
CHANGED
|
@@ -143,13 +143,6 @@ export function apply(ctx, config = {}) {
|
|
|
143
143
|
logger.warn?.('[dsh-goal] Legacy migration check failed:', migErr.message);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
// Register addon tools: goal_milestones and goal_checklist
|
|
147
|
-
registerTools(ctx, {
|
|
148
|
-
milestoneManager,
|
|
149
|
-
goalsService: safeService(ctx, "goals"),
|
|
150
|
-
logger,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
146
|
// Support fallback engine for tests or non-core environments
|
|
154
147
|
let fallbackEngine = null;
|
|
155
148
|
if (!safeService(ctx, "goals")) {
|
|
@@ -168,12 +161,22 @@ export function apply(ctx, config = {}) {
|
|
|
168
161
|
autoScaleBudgetNearCompletion: currentSettings.autoScaleBudgetNearCompletion,
|
|
169
162
|
enableTemplatesDrawer: currentSettings.enableTemplatesDrawer,
|
|
170
163
|
enableMilestoneDependencies: currentSettings.enableMilestoneDependencies,
|
|
164
|
+
enableMilestoneCompaction: currentSettings.enableMilestoneCompaction,
|
|
171
165
|
approvalGate: safeService(ctx, "approvalGate"),
|
|
172
166
|
logger,
|
|
173
167
|
});
|
|
168
|
+
ctx.goalEngine = fallbackEngine;
|
|
174
169
|
registerPlanModeGuard(ctx, { engine: fallbackEngine, logger });
|
|
175
170
|
}
|
|
176
171
|
|
|
172
|
+
// Register addon tools: goal_milestones, goal_checklist, goal_spawn_subgoal
|
|
173
|
+
registerTools(ctx, {
|
|
174
|
+
milestoneManager,
|
|
175
|
+
goalsService: safeService(ctx, "goals"),
|
|
176
|
+
engine: fallbackEngine,
|
|
177
|
+
logger,
|
|
178
|
+
});
|
|
179
|
+
|
|
177
180
|
// 1. One-click auto-updater registration (Issue #99)
|
|
178
181
|
if (ctx?.webServer?.register) {
|
|
179
182
|
if (typeof ctx?.effect === 'function') {
|
|
@@ -335,7 +338,15 @@ export function apply(ctx, config = {}) {
|
|
|
335
338
|
|
|
336
339
|
// Compatibility events for turn/end and approval/asked
|
|
337
340
|
ctx.on('turn/end', (event) => {
|
|
338
|
-
|
|
341
|
+
if (!fallbackEngine || !event) return;
|
|
342
|
+
const sid = sessionIdOf(event?.agent || event, 'default');
|
|
343
|
+
const usage = event?.usage || event?.tokenUsage || event?.tokens || event?.response?.usage;
|
|
344
|
+
if (usage) {
|
|
345
|
+
fallbackEngine.addTokenUsage({
|
|
346
|
+
...usage,
|
|
347
|
+
modelName: event?.model || event?.modelName || event?.response?.model || '',
|
|
348
|
+
}, sid);
|
|
349
|
+
}
|
|
339
350
|
});
|
|
340
351
|
|
|
341
352
|
ctx.on('approval/asked', (event) => {
|
package/lib/routes.js
CHANGED
|
@@ -5,6 +5,7 @@ import { isValidGitRef } from './engine-git-branch.js';
|
|
|
5
5
|
import { saveGoalArtifact, rollbackToCheckpoint } from './engine-reports.js';
|
|
6
6
|
import { getTemplatesCatalogue, instantiateTemplate } from './engine-templates.js';
|
|
7
7
|
import { parseIssueChecklist } from './engine-issue-sync.js';
|
|
8
|
+
import { dispatchGoalTelegramNotification } from './engine-release-notes.js';
|
|
8
9
|
|
|
9
10
|
export function isLoopback(value) {
|
|
10
11
|
if (!value) return false;
|
|
@@ -490,6 +491,8 @@ export function registerRoutes(ctx, {
|
|
|
490
491
|
cwd: safeStartCwd,
|
|
491
492
|
issueBody: data.issueBody,
|
|
492
493
|
mode: data.mode,
|
|
494
|
+
maxCostBudget: data.maxCostBudget,
|
|
495
|
+
currency: data.currency,
|
|
493
496
|
}, sid);
|
|
494
497
|
} else {
|
|
495
498
|
let parsedMilestones = [];
|
|
@@ -873,6 +876,31 @@ export function registerRoutes(ctx, {
|
|
|
873
876
|
break;
|
|
874
877
|
}
|
|
875
878
|
|
|
879
|
+
case 'spawn_subgoal': {
|
|
880
|
+
const subTitle = typeof data.title === 'string' ? data.title.trim() : '';
|
|
881
|
+
if (!subTitle) {
|
|
882
|
+
res.statusCode = 400;
|
|
883
|
+
return res.end(JSON.stringify({ error: 'Sub-goal title is required' }));
|
|
884
|
+
}
|
|
885
|
+
if (engine) {
|
|
886
|
+
const spawnRes = engine.spawnSubGoal({
|
|
887
|
+
title: subTitle,
|
|
888
|
+
prompt: data.prompt || data.description,
|
|
889
|
+
maxIterations: data.maxIterations ?? data.max_iterations,
|
|
890
|
+
inheritContext: data.inheritContext ?? data.inherit_context,
|
|
891
|
+
}, sid);
|
|
892
|
+
if (!spawnRes.ok) {
|
|
893
|
+
res.statusCode = 400;
|
|
894
|
+
return res.end(JSON.stringify({ error: spawnRes.error || 'Failed to spawn sub-goal' }));
|
|
895
|
+
}
|
|
896
|
+
result = spawnRes;
|
|
897
|
+
} else {
|
|
898
|
+
res.statusCode = 404;
|
|
899
|
+
result = { ok: false, error: 'Goal engine unavailable' };
|
|
900
|
+
}
|
|
901
|
+
break;
|
|
902
|
+
}
|
|
903
|
+
|
|
876
904
|
case 'instantiate_template': {
|
|
877
905
|
const tplId = data.templateId || data.id;
|
|
878
906
|
if (!tplId) {
|
|
@@ -888,6 +916,29 @@ export function registerRoutes(ctx, {
|
|
|
888
916
|
return res.end(JSON.stringify({ ok: true, template: instance }));
|
|
889
917
|
}
|
|
890
918
|
|
|
919
|
+
case 'export_release_notes': {
|
|
920
|
+
const snap = engine ? engine.getSnapshot(sid) : null;
|
|
921
|
+
if (engine && (!snap || !snap.hasActiveGoal)) {
|
|
922
|
+
res.statusCode = 404;
|
|
923
|
+
return res.end(JSON.stringify({ error: 'No active goal found to export release notes' }));
|
|
924
|
+
}
|
|
925
|
+
const markdown = engine ? engine.exportReleaseNotes(sid, data.options || {}) : '';
|
|
926
|
+
res.statusCode = 200;
|
|
927
|
+
return res.end(JSON.stringify({ ok: true, releaseNotes: markdown, state: snap || {} }));
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
case 'notify_telegram': {
|
|
931
|
+
const snap = engine ? engine.getSnapshot(sid) : null;
|
|
932
|
+
if (engine && (!snap || !snap.hasActiveGoal)) {
|
|
933
|
+
res.statusCode = 404;
|
|
934
|
+
return res.end(JSON.stringify({ error: 'No active goal found to dispatch notification' }));
|
|
935
|
+
}
|
|
936
|
+
const goal = engine ? engine.getGoal(sid) : null;
|
|
937
|
+
const notifyRes = await dispatchGoalTelegramNotification(goal, { engine, ctx, ...(data.options || {}) });
|
|
938
|
+
res.statusCode = notifyRes.ok ? 200 : 500;
|
|
939
|
+
return res.end(JSON.stringify(notifyRes));
|
|
940
|
+
}
|
|
941
|
+
|
|
891
942
|
default:
|
|
892
943
|
res.statusCode = 400;
|
|
893
944
|
return res.end(JSON.stringify({ error: `Unknown action: ${action}` }));
|
package/lib/tools.js
CHANGED
|
@@ -376,5 +376,74 @@ export function registerTools(ctx, { milestoneManager, goalsService, logger = co
|
|
|
376
376
|
output: JSON_OUTPUT,
|
|
377
377
|
execute: handleGoalAskOperator,
|
|
378
378
|
});
|
|
379
|
+
|
|
380
|
+
// Tool 7: goal_spawn_subgoal (Issue #170)
|
|
381
|
+
const handleGoalSpawnSubgoal = async (args = {}, toolCtx = {}) => {
|
|
382
|
+
const parentSessionId = args.goalId || args.goal_id || resolveGoalId(toolCtx);
|
|
383
|
+
const title = args.title || args.goal;
|
|
384
|
+
if (!title || typeof title !== 'string' || !title.trim()) {
|
|
385
|
+
throw new Error('Field "title" is required and must be a non-empty string');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const engine = ctx.goalEngine || (ctx.get && ctx.get('goalEngine'));
|
|
389
|
+
if (engine && typeof engine.spawnSubGoal === 'function') {
|
|
390
|
+
const res = engine.spawnSubGoal({
|
|
391
|
+
title: title.trim(),
|
|
392
|
+
prompt: args.prompt || args.description,
|
|
393
|
+
maxIterations: args.max_iterations ?? args.maxIterations,
|
|
394
|
+
inheritContext: args.inherit_context ?? args.inheritContext,
|
|
395
|
+
}, parentSessionId);
|
|
396
|
+
|
|
397
|
+
if (!res.ok) {
|
|
398
|
+
throw new Error(`Failed to spawn sub-goal: ${res.error}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
ok: true,
|
|
403
|
+
parentGoalId: res.parentGoalId,
|
|
404
|
+
parentSessionId: res.parentSessionId,
|
|
405
|
+
subGoalId: res.subGoalId,
|
|
406
|
+
childSessionId: res.childSessionId,
|
|
407
|
+
title: title.trim(),
|
|
408
|
+
message: 'Sub-goal spawned with isolated execution context. Complete sub-milestones and invoke goal_finish to conclude.',
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
ok: true,
|
|
414
|
+
parentSessionId,
|
|
415
|
+
title: title.trim(),
|
|
416
|
+
message: 'Sub-goal registered (fallback mode).',
|
|
417
|
+
};
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
safeRegister({
|
|
421
|
+
name: 'goal_spawn_subgoal',
|
|
422
|
+
description: 'Spawn a child sub-goal with isolated context and task checklist (#170). When all sub-milestones are completed, invoking goal_finish returns deliverables and control to the parent goal.',
|
|
423
|
+
parameters: {
|
|
424
|
+
type: 'object',
|
|
425
|
+
properties: {
|
|
426
|
+
title: {
|
|
427
|
+
type: 'string',
|
|
428
|
+
description: 'Clear, actionable title of the sub-goal',
|
|
429
|
+
},
|
|
430
|
+
prompt: {
|
|
431
|
+
type: 'string',
|
|
432
|
+
description: 'Optional context, detailed instructions, or constraints for the sub-goal',
|
|
433
|
+
},
|
|
434
|
+
max_iterations: {
|
|
435
|
+
type: 'number',
|
|
436
|
+
description: 'Maximum iteration budget for the sub-goal (default: 15)',
|
|
437
|
+
},
|
|
438
|
+
inherit_context: {
|
|
439
|
+
type: 'boolean',
|
|
440
|
+
description: 'Whether the child sub-goal inherits parent goal context summary (default: true)',
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
required: ['title'],
|
|
444
|
+
},
|
|
445
|
+
output: JSON_OUTPUT,
|
|
446
|
+
execute: handleGoalSpawnSubgoal,
|
|
447
|
+
});
|
|
379
448
|
});
|
|
380
449
|
}
|
package/package.json
CHANGED