@goodandready/dsh-goal 0.2.30 → 0.2.32
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 +502 -5
- package/lib/engine-compaction.js +126 -0
- package/lib/engine-diff.js +147 -0
- package/lib/engine-kanban-sync.js +242 -0
- package/lib/engine-prompt.js +37 -5
- package/lib/engine-subgoals.js +207 -0
- package/lib/goal-engine-constants.js +12 -0
- package/lib/goal-engine.js +38 -47
- package/lib/index.js +10 -7
- package/lib/routes.js +71 -1
- package/lib/tools.js +69 -0
- package/package.json +1 -1
|
@@ -192,6 +192,12 @@ export function buildSnapshot(goal, sid, engine) {
|
|
|
192
192
|
enableMilestoneDependencies: engine.enableMilestoneDependencies ?? true,
|
|
193
193
|
soundScheme: engine.soundScheme ?? 'default',
|
|
194
194
|
enableVoiceAnnouncements: engine.enableVoiceAnnouncements ?? false,
|
|
195
|
+
enableMilestoneCompaction: engine.enableMilestoneCompaction ?? true,
|
|
196
|
+
parentGoalId: null,
|
|
197
|
+
parentSessionId: null,
|
|
198
|
+
isSubGoal: false,
|
|
199
|
+
subGoals: [],
|
|
200
|
+
breadcrumbs: [],
|
|
195
201
|
operatorQuery: null,
|
|
196
202
|
waitingForOperator: false,
|
|
197
203
|
};
|
|
@@ -253,6 +259,12 @@ export function buildSnapshot(goal, sid, engine) {
|
|
|
253
259
|
enableMilestoneDependencies: engine.enableMilestoneDependencies ?? true,
|
|
254
260
|
soundScheme: engine.soundScheme ?? 'default',
|
|
255
261
|
enableVoiceAnnouncements: engine.enableVoiceAnnouncements ?? false,
|
|
262
|
+
enableMilestoneCompaction: engine.enableMilestoneCompaction ?? true,
|
|
263
|
+
parentGoalId: goal.parentGoalId || null,
|
|
264
|
+
parentSessionId: goal.parentSessionId || null,
|
|
265
|
+
isSubGoal: Boolean(goal.parentGoalId || goal.isSubGoal),
|
|
266
|
+
subGoals: Array.isArray(goal.subGoals) ? goal.subGoals : [],
|
|
267
|
+
breadcrumbs: typeof engine.getSubGoalBreadcrumbs === "function" ? engine.getSubGoalBreadcrumbs(sid) : [],
|
|
256
268
|
operatorQuery: goal.operatorQuery || null,
|
|
257
269
|
waitingForOperator: Boolean(goal.waitingForOperator),
|
|
258
270
|
};
|
package/lib/goal-engine.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { verifyMilestoneAcceptance } from './engine-verification.js';
|
|
2
|
+
import { getEngineGoalDiff } from './engine-diff.js';
|
|
3
|
+
import { syncGoalToKanban, syncMilestoneStatusToKanban, handleKanbanCardMoved } from './engine-kanban-sync.js';
|
|
2
4
|
import { askOperatorQuery, answerOperatorQuery } from './engine-operator-query.js';
|
|
3
5
|
import { GoalMode, GoalState, MilestoneStatus, formatElapsed, formatETA, detectLanguage, sessionIdOf, calculateElapsedSeconds, calculateRemainingSeconds, buildSnapshot, getSafeCwd } from './goal-engine-constants.js';
|
|
4
6
|
import { getGitCurrentCommit, exportReportMarkdown, exportReportGitHubPR, createMilestoneCheckpoint, rollbackToCheckpoint, saveGoalArtifact } from './engine-reports.js';
|
|
@@ -11,8 +13,10 @@ import { parseIssueRef, parseIssueChecklist, syncMilestoneWithIssue } from './en
|
|
|
11
13
|
import { generateRetrospectiveData } from './engine-retrospective.js';
|
|
12
14
|
import { generatePreplanDraft } from './engine-templates.js';
|
|
13
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';
|
|
14
18
|
|
|
15
|
-
export { GoalMode, GoalState, MilestoneStatus, formatElapsed, formatETA, detectLanguage, sessionIdOf, matchMilestone, parseIssueChecklist, getGitCurrentCommit, exportReportMarkdown, exportReportGitHubPR, createMilestoneCheckpoint, rollbackToCheckpoint, saveGoalArtifact };
|
|
19
|
+
export { GoalMode, GoalState, MilestoneStatus, formatElapsed, formatETA, detectLanguage, sessionIdOf, matchMilestone, parseIssueChecklist, getGitCurrentCommit, exportReportMarkdown, exportReportGitHubPR, createMilestoneCheckpoint, rollbackToCheckpoint, saveGoalArtifact, compactMilestone, estimateCompactionSavings, spawnSubGoal, completeSubGoal, getSubGoalBreadcrumbs, getSubGoalsList };
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
22
|
* Isolated Goal Management Engine for DeepSeek Harness
|
|
@@ -31,8 +35,8 @@ export class GoalEngine {
|
|
|
31
35
|
enableLiveActivityFeed: options.enableLiveActivityFeed ?? true, enablePreplanning: options.enablePreplanning ?? true,
|
|
32
36
|
autoBranchOnGoalStart: options.autoBranchOnGoalStart ?? true, enablePostGoalRetrospective: options.enablePostGoalRetrospective ?? true,
|
|
33
37
|
autoScaleBudgetNearCompletion: options.autoScaleBudgetNearCompletion ?? false, enableTemplatesDrawer: options.enableTemplatesDrawer ?? true,
|
|
34
|
-
enableMilestoneDependencies: options.enableMilestoneDependencies ?? true, soundScheme: options.soundScheme ?? 'default',
|
|
35
|
-
enableVoiceAnnouncements: options.enableVoiceAnnouncements ?? false, approvalGate: options.approvalGate || null,
|
|
38
|
+
enableMilestoneDependencies: options.enableMilestoneDependencies ?? true, soundScheme: options.soundScheme ?? 'default', enableKanbanSync: options.enableKanbanSync ?? true, ctx: options.ctx || null, kanbanService: options.kanbanService || null, kanbanStore: options.kanbanStore || null,
|
|
39
|
+
enableVoiceAnnouncements: options.enableVoiceAnnouncements ?? false, enableMilestoneCompaction: options.enableMilestoneCompaction ?? true, approvalGate: options.approvalGate || null,
|
|
36
40
|
checkApproval: options.checkApproval || null, maxSessions: options.maxSessions ?? 100,
|
|
37
41
|
});
|
|
38
42
|
this.goals = new Map();
|
|
@@ -56,10 +60,7 @@ export class GoalEngine {
|
|
|
56
60
|
flushSync() { this.store.flushSync(); }
|
|
57
61
|
saveStateToDisk() { this.flushSync(); }
|
|
58
62
|
|
|
59
|
-
subscribe(callback) {
|
|
60
|
-
this.listeners.add(callback);
|
|
61
|
-
return () => this.listeners.delete(callback);
|
|
62
|
-
}
|
|
63
|
+
subscribe(callback) { this.listeners.add(callback); return () => this.listeners.delete(callback); }
|
|
63
64
|
|
|
64
65
|
emit(sessionId = 'default', immediate = false) {
|
|
65
66
|
this.scheduleSave(immediate);
|
|
@@ -74,21 +75,9 @@ export class GoalEngine {
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
recordProgress(sessionId = 'default') {
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
incrementStallCount(sessionId = 'default') {
|
|
82
|
-
const sid = sessionId || 'default';
|
|
83
|
-
const current = this.stallCounters.get(sid) || 0;
|
|
84
|
-
const next = current + 1;
|
|
85
|
-
this.stallCounters.set(sid, next);
|
|
86
|
-
return next;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
getStallCount(sessionId = 'default') {
|
|
90
|
-
return this.stallCounters.get(sessionId || 'default') || 0;
|
|
91
|
-
}
|
|
78
|
+
recordProgress(sessionId = 'default') { this.stallCounters.set(sessionId || 'default', 0); }
|
|
79
|
+
incrementStallCount(sessionId = 'default') { const sid = sessionId || 'default'; const n = (this.stallCounters.get(sid) || 0) + 1; this.stallCounters.set(sid, n); return n; }
|
|
80
|
+
getStallCount(sessionId = 'default') { return this.stallCounters.get(sessionId || 'default') || 0; }
|
|
92
81
|
|
|
93
82
|
updateConfig(config = {}) {
|
|
94
83
|
const rawMax = config.defaultMaxIterations ?? config.maxIterations;
|
|
@@ -102,7 +91,7 @@ export class GoalEngine {
|
|
|
102
91
|
}
|
|
103
92
|
}
|
|
104
93
|
}
|
|
105
|
-
const boolKeys = ['autoDrive', 'enableSound', 'showQuickLaunchButton', 'autoCheckpointOnMilestone', 'enableLiveActivityFeed', 'enablePreplanning', 'autoBranchOnGoalStart', 'enablePostGoalRetrospective', 'autoScaleBudgetNearCompletion', 'enableTemplatesDrawer', 'enableMilestoneDependencies', 'enableVoiceAnnouncements'];
|
|
94
|
+
const boolKeys = ['autoDrive', 'enableSound', 'showQuickLaunchButton', 'autoCheckpointOnMilestone', 'enableLiveActivityFeed', 'enablePreplanning', 'autoBranchOnGoalStart', 'enablePostGoalRetrospective', 'autoScaleBudgetNearCompletion', 'enableTemplatesDrawer', 'enableMilestoneDependencies', 'enableVoiceAnnouncements', 'enableMilestoneCompaction'];
|
|
106
95
|
for (const k of boolKeys) if (typeof config[k] === 'boolean') this[k] = config[k];
|
|
107
96
|
if (typeof config.consecutiveToolFailureLimit === 'number') this.consecutiveToolFailureLimit = Math.max(0, config.consecutiveToolFailureLimit);
|
|
108
97
|
if (typeof config.maxTokenBudget === 'number') this.maxTokenBudget = Math.max(0, config.maxTokenBudget);
|
|
@@ -180,7 +169,7 @@ export class GoalEngine {
|
|
|
180
169
|
title: cleanTitle,
|
|
181
170
|
description: options.description?.trim() || '',
|
|
182
171
|
lang: options.lang || detectLanguage(cleanTitle),
|
|
183
|
-
cwd: options.cwd ? getSafeCwd(options.cwd) : null,
|
|
172
|
+
cwd: options.cwd ? getSafeCwd(options.cwd, options.allowedRoots || this.allowedRoots || []) : null,
|
|
184
173
|
mode: initialMode,
|
|
185
174
|
state: initialState,
|
|
186
175
|
startedAt: now,
|
|
@@ -193,7 +182,7 @@ export class GoalEngine {
|
|
|
193
182
|
budgetWarningTriggered: false,
|
|
194
183
|
budgetAutoScaled: false,
|
|
195
184
|
gitStartCommit: gitCommit || null,
|
|
196
|
-
gitBranch: null,
|
|
185
|
+
gitBranch: options.gitBranch || options.branchName || null,
|
|
197
186
|
gitParentBranch: null,
|
|
198
187
|
pendingNudge: null,
|
|
199
188
|
nudges: [],
|
|
@@ -206,6 +195,10 @@ export class GoalEngine {
|
|
|
206
195
|
issueRef: parseIssueRef(cleanTitle),
|
|
207
196
|
operatorQuery: null,
|
|
208
197
|
waitingForOperator: false,
|
|
198
|
+
parentGoalId: options.parentGoalId || null,
|
|
199
|
+
parentSessionId: options.parentSessionId || null,
|
|
200
|
+
isSubGoal: Boolean(options.parentGoalId),
|
|
201
|
+
subGoals: [],
|
|
209
202
|
tokensUsage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
210
203
|
logs: [{
|
|
211
204
|
timestamp: now,
|
|
@@ -303,19 +296,18 @@ export class GoalEngine {
|
|
|
303
296
|
m.status = MilestoneStatus.COMPLETED;
|
|
304
297
|
}
|
|
305
298
|
}
|
|
299
|
+
if (goal.parentSessionId) syncSubGoalToParent(this, sid, summary);
|
|
306
300
|
this.stallCounters.delete(sid);
|
|
307
301
|
this.toolFailureCounters.delete(sid);
|
|
308
302
|
this.emit(sid, true);
|
|
309
303
|
return this.getSnapshot(sid);
|
|
310
304
|
}
|
|
311
305
|
|
|
312
|
-
setMilestones(milestonesList, sessionId = 'default') {
|
|
313
|
-
this.addMilestones(milestonesList, true, sessionId);
|
|
314
|
-
return this.getSnapshot(sessionId);
|
|
315
|
-
}
|
|
306
|
+
setMilestones(milestonesList, sessionId = 'default') { this.addMilestones(milestonesList, true, sessionId); return this.getSnapshot(sessionId); }
|
|
316
307
|
|
|
317
308
|
addMilestones(milestonesList, shouldEmit = true, sessionId = 'default') {
|
|
318
|
-
const sid = sessionId || 'default';
|
|
309
|
+
const sid = typeof shouldEmit === 'string' ? shouldEmit : (sessionId || 'default');
|
|
310
|
+
const shouldEmitFlag = typeof shouldEmit === 'boolean' ? shouldEmit : true;
|
|
319
311
|
const goal = this.goals.get(sid);
|
|
320
312
|
if (!goal || !Array.isArray(milestonesList)) return [];
|
|
321
313
|
|
|
@@ -325,7 +317,7 @@ export class GoalEngine {
|
|
|
325
317
|
}
|
|
326
318
|
this.recordProgress(sid);
|
|
327
319
|
recordActivity(goal, { type: 'milestone', tool: 'addMilestones', status: 'success', details: `Added ${newMilestones.length} milestones` });
|
|
328
|
-
if (
|
|
320
|
+
if (shouldEmitFlag) this.emit(sid);
|
|
329
321
|
return newMilestones;
|
|
330
322
|
}
|
|
331
323
|
|
|
@@ -359,8 +351,10 @@ export class GoalEngine {
|
|
|
359
351
|
|
|
360
352
|
const prevStatus = res.prevStatus;
|
|
361
353
|
recordActivity(goal, { type: 'milestone', tool: 'updateMilestone', status: 'success', details: `${id} -> ${status}` });
|
|
354
|
+
if (this.enableKanbanSync) syncMilestoneStatusToKanban(target, { goal, ctx: this.ctx, store: this.kanbanStore, kanbanService: this.kanbanService }).catch(() => {});
|
|
362
355
|
|
|
363
356
|
if (target.status === MilestoneStatus.COMPLETED && prevStatus !== MilestoneStatus.COMPLETED) {
|
|
357
|
+
if (this.enableMilestoneCompaction !== false) compactMilestone(target, { goal, cwd: goal.cwd });
|
|
364
358
|
if (this.autoCheckpointOnMilestone) {
|
|
365
359
|
const commitHash = createMilestoneCheckpoint(target, sid);
|
|
366
360
|
if (commitHash) {
|
|
@@ -435,9 +429,7 @@ export class GoalEngine {
|
|
|
435
429
|
return true;
|
|
436
430
|
}
|
|
437
431
|
|
|
438
|
-
getElapsedSeconds(sessionId = 'default') {
|
|
439
|
-
return calculateElapsedSeconds(this.goals.get(sessionId || 'default'));
|
|
440
|
-
}
|
|
432
|
+
getElapsedSeconds(sessionId = 'default') { return calculateElapsedSeconds(this.goals.get(sessionId || 'default')); }
|
|
441
433
|
|
|
442
434
|
addTokenUsage(usage, sessionId = 'default') {
|
|
443
435
|
if (!usage || typeof usage !== 'object') return;
|
|
@@ -467,19 +459,9 @@ export class GoalEngine {
|
|
|
467
459
|
this.emit(sid);
|
|
468
460
|
}
|
|
469
461
|
|
|
470
|
-
getEstimatedRemainingSeconds(sessionId = 'default') {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
getSnapshot(sessionId = 'default') {
|
|
476
|
-
const sid = sessionId || 'default';
|
|
477
|
-
return buildSnapshot(this.goals.get(sid), sid, this);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
getGoalSnapshot(sessionId = 'default') {
|
|
481
|
-
return this.getSnapshot(sessionId);
|
|
482
|
-
}
|
|
462
|
+
getEstimatedRemainingSeconds(sessionId = 'default') { const sid = sessionId || 'default'; return calculateRemainingSeconds(this.goals.get(sid), this.getElapsedSeconds(sid)); }
|
|
463
|
+
getSnapshot(sessionId = 'default') { const sid = sessionId || 'default'; return buildSnapshot(this.goals.get(sid), sid, this); }
|
|
464
|
+
getGoalSnapshot(sessionId = 'default') { return this.getSnapshot(sessionId); }
|
|
483
465
|
|
|
484
466
|
getStatePromptInjection(sessionId = 'default') {
|
|
485
467
|
const sid = sessionId || 'default';
|
|
@@ -579,8 +561,17 @@ export class GoalEngine {
|
|
|
579
561
|
return res;
|
|
580
562
|
}
|
|
581
563
|
|
|
564
|
+
getDiff(sessionId = 'default', options = {}) { return getEngineGoalDiff(this, sessionId, options); }
|
|
565
|
+
syncKanban(sessionId = 'default', options = {}) { return syncGoalToKanban(this, sessionId, options); }
|
|
566
|
+
handleKanbanCardMove(payload = {}, sessionId = 'default') { return handleKanbanCardMoved(this, payload, sessionId); }
|
|
567
|
+
|
|
582
568
|
isPlanMode(sessionId = 'default') {
|
|
583
569
|
const goal = this.goals.get(sessionId || 'default');
|
|
584
570
|
return Boolean(goal && (goal.mode === GoalMode.PLAN || goal.state === GoalState.PLANNING));
|
|
585
571
|
}
|
|
572
|
+
spawnSubGoal(options = {}, sessionId = 'default') { return spawnSubGoal(this, sessionId, options); }
|
|
573
|
+
completeSubGoal(sessionId, summary = '') { return completeSubGoal(this, sessionId, summary); }
|
|
574
|
+
getSubGoals(sessionId = 'default') { return getSubGoalsList(this, sessionId); }
|
|
575
|
+
getSubGoalBreadcrumbs(sessionId = 'default') { return getSubGoalBreadcrumbs(this, sessionId); }
|
|
576
|
+
getCompactionSavings(sessionId = 'default') { const g = this.goals.get(sessionId || 'default'); return estimateCompactionSavings(g?.milestones || [], g?.lang || 'en'); }
|
|
586
577
|
}
|
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') {
|
package/lib/routes.js
CHANGED
|
@@ -383,6 +383,28 @@ export function registerRoutes(ctx, {
|
|
|
383
383
|
}));
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
+
// GET /dsh-goal/diff
|
|
387
|
+
if (req.method === 'GET' && (pathname === '/dsh-goal/diff' || pathname === '/dsh-goal/diff/')) {
|
|
388
|
+
const rejection = getRequestRejection(req, { ctx });
|
|
389
|
+
if (rejection !== undefined) {
|
|
390
|
+
res.statusCode = rejection;
|
|
391
|
+
return res.end(JSON.stringify({ error: rejection === 401 ? 'Unauthorized: authentication required' : 'Forbidden: untrusted caller origin' }));
|
|
392
|
+
}
|
|
393
|
+
const sid = sessionIdOf(req, 'default');
|
|
394
|
+
const target = url.searchParams.get('target') || undefined;
|
|
395
|
+
const base = url.searchParams.get('base') || undefined;
|
|
396
|
+
const file = url.searchParams.get('file') || undefined;
|
|
397
|
+
const cwd = url.searchParams.get('cwd') || undefined;
|
|
398
|
+
|
|
399
|
+
if (engine) {
|
|
400
|
+
const diffResult = engine.getDiff(sid, { target, base, file, cwd });
|
|
401
|
+
res.statusCode = diffResult.ok ? 200 : 400;
|
|
402
|
+
return res.end(JSON.stringify(diffResult));
|
|
403
|
+
}
|
|
404
|
+
res.statusCode = 404;
|
|
405
|
+
return res.end(JSON.stringify({ ok: false, error: 'Goal engine unavailable' }));
|
|
406
|
+
}
|
|
407
|
+
|
|
386
408
|
// POST /dsh-goal/action
|
|
387
409
|
// CSRF & Same-Origin guard
|
|
388
410
|
if (req.method === 'POST' && (pathname === '/dsh-goal/action' || pathname === '/dsh-goal/action/')) {
|
|
@@ -429,7 +451,7 @@ export function registerRoutes(ctx, {
|
|
|
429
451
|
body += chunk;
|
|
430
452
|
};
|
|
431
453
|
|
|
432
|
-
const onEnd = () => {
|
|
454
|
+
const onEnd = async () => {
|
|
433
455
|
if (limitExceeded) return;
|
|
434
456
|
try {
|
|
435
457
|
const data = JSON.parse(body || '{}');
|
|
@@ -829,6 +851,53 @@ export function registerRoutes(ctx, {
|
|
|
829
851
|
res.statusCode = 200;
|
|
830
852
|
return res.end(JSON.stringify({ ok: true, templates: getTemplatesCatalogue() }));
|
|
831
853
|
|
|
854
|
+
case 'kanban_move': {
|
|
855
|
+
if (engine) {
|
|
856
|
+
result = engine.handleKanbanCardMove(data, sid);
|
|
857
|
+
if (!result.ok) res.statusCode = 400;
|
|
858
|
+
} else {
|
|
859
|
+
res.statusCode = 404;
|
|
860
|
+
result = { ok: false, error: 'Goal engine unavailable' };
|
|
861
|
+
}
|
|
862
|
+
break;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
case 'kanban_sync': {
|
|
866
|
+
if (engine) {
|
|
867
|
+
result = await engine.syncKanban(sid, { ctx });
|
|
868
|
+
if (!result.ok && !result.skipped) res.statusCode = 400;
|
|
869
|
+
} else {
|
|
870
|
+
res.statusCode = 404;
|
|
871
|
+
result = { ok: false, error: 'Goal engine unavailable' };
|
|
872
|
+
}
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
case 'spawn_subgoal': {
|
|
877
|
+
const subTitle = typeof data.title === 'string' ? data.title.trim() : '';
|
|
878
|
+
if (!subTitle) {
|
|
879
|
+
res.statusCode = 400;
|
|
880
|
+
return res.end(JSON.stringify({ error: 'Sub-goal title is required' }));
|
|
881
|
+
}
|
|
882
|
+
if (engine) {
|
|
883
|
+
const spawnRes = engine.spawnSubGoal({
|
|
884
|
+
title: subTitle,
|
|
885
|
+
prompt: data.prompt || data.description,
|
|
886
|
+
maxIterations: data.maxIterations ?? data.max_iterations,
|
|
887
|
+
inheritContext: data.inheritContext ?? data.inherit_context,
|
|
888
|
+
}, sid);
|
|
889
|
+
if (!spawnRes.ok) {
|
|
890
|
+
res.statusCode = 400;
|
|
891
|
+
return res.end(JSON.stringify({ error: spawnRes.error || 'Failed to spawn sub-goal' }));
|
|
892
|
+
}
|
|
893
|
+
result = spawnRes;
|
|
894
|
+
} else {
|
|
895
|
+
res.statusCode = 404;
|
|
896
|
+
result = { ok: false, error: 'Goal engine unavailable' };
|
|
897
|
+
}
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
900
|
+
|
|
832
901
|
case 'instantiate_template': {
|
|
833
902
|
const tplId = data.templateId || data.id;
|
|
834
903
|
if (!tplId) {
|
|
@@ -870,6 +939,7 @@ export function registerRoutes(ctx, {
|
|
|
870
939
|
'/dsh-goal/events': ['GET'],
|
|
871
940
|
'/dsh-goal/templates': ['GET'],
|
|
872
941
|
'/dsh-goal/state': ['GET'],
|
|
942
|
+
'/dsh-goal/diff': ['GET'],
|
|
873
943
|
'/dsh-goal/action': ['POST'],
|
|
874
944
|
};
|
|
875
945
|
const cleanPath = pathname.replace(/\/+$/, '') || '/';
|
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