@goodandready/dsh-goal 0.2.9 → 0.2.11
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 +350 -13
- package/lib/engine-git-branch.js +105 -0
- package/lib/engine-intervention.js +150 -0
- package/lib/engine-issue-sync.js +194 -0
- package/lib/engine-milestones.js +42 -3
- package/lib/engine-prompt.js +28 -4
- package/lib/engine-retrospective.js +79 -0
- package/lib/engine-sessions.js +82 -0
- package/lib/engine-templates.js +167 -0
- package/lib/goal-engine-constants.js +30 -0
- package/lib/goal-engine.js +212 -236
- package/lib/index.js +54 -27
- package/lib/routes.js +97 -7
- package/lib/tools.js +19 -2
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -22,6 +22,15 @@ export const Config = z.object({
|
|
|
22
22
|
maxTokenBudget: z.number().default(0).description('Maximum token budget per goal session (0 to disable)'),
|
|
23
23
|
budgetWarningThreshold: z.number().default(80).description('Percentage of token budget consumed before model warning injection (e.g. 80)'),
|
|
24
24
|
autoCheckpointOnMilestone: z.boolean().default(false).description('Automatically create git commit checkpoint upon milestone completion'),
|
|
25
|
+
enableLiveActivityFeed: z.boolean().default(true).description('Display compact recent tool and control activity feed in details modal'),
|
|
26
|
+
enablePreplanning: z.boolean().default(true).description('Generate initial draft plan for approval before launching autonomous turns'),
|
|
27
|
+
autoBranchOnGoalStart: z.boolean().default(true).description('Automatically create dedicated isolated git branch for goal execution'),
|
|
28
|
+
enablePostGoalRetrospective: z.boolean().default(true).description('Generate structured retrospective and analytics card upon goal completion'),
|
|
29
|
+
autoScaleBudgetNearCompletion: z.boolean().default(false).description('Automatically grant token buffer extension if budget reaches 100% near completion'),
|
|
30
|
+
enableTemplatesDrawer: z.boolean().default(true).description('Provide pre-configured engineering goal templates in UI drawer'),
|
|
31
|
+
enableMilestoneDependencies: z.boolean().default(true).description('Enforce milestone dependency ordering and block premature execution'),
|
|
32
|
+
soundScheme: z.string().default('default').description('Audio theme for status and milestone notifications'),
|
|
33
|
+
enableVoiceAnnouncements: z.boolean().default(false).description('Use Web Speech API for voice announcements on milestone events'),
|
|
25
34
|
storagePath: z.string().default('').description('Custom filesystem path for persistent state storage'),
|
|
26
35
|
});
|
|
27
36
|
|
|
@@ -43,6 +52,15 @@ export function apply(ctx, config = {}) {
|
|
|
43
52
|
maxTokenBudget: config?.maxTokenBudget ?? 0,
|
|
44
53
|
budgetWarningThreshold: config?.budgetWarningThreshold ?? 80,
|
|
45
54
|
autoCheckpointOnMilestone: config?.autoCheckpointOnMilestone ?? false,
|
|
55
|
+
enableLiveActivityFeed: config?.enableLiveActivityFeed ?? true,
|
|
56
|
+
enablePreplanning: config?.enablePreplanning ?? true,
|
|
57
|
+
autoBranchOnGoalStart: config?.autoBranchOnGoalStart ?? true,
|
|
58
|
+
enablePostGoalRetrospective: config?.enablePostGoalRetrospective ?? true,
|
|
59
|
+
autoScaleBudgetNearCompletion: config?.autoScaleBudgetNearCompletion ?? false,
|
|
60
|
+
enableTemplatesDrawer: config?.enableTemplatesDrawer ?? true,
|
|
61
|
+
enableMilestoneDependencies: config?.enableMilestoneDependencies ?? true,
|
|
62
|
+
soundScheme: config?.soundScheme ?? 'default',
|
|
63
|
+
enableVoiceAnnouncements: config?.enableVoiceAnnouncements ?? false,
|
|
46
64
|
storagePath: config?.storagePath,
|
|
47
65
|
};
|
|
48
66
|
|
|
@@ -60,6 +78,15 @@ export function apply(ctx, config = {}) {
|
|
|
60
78
|
maxTokenBudget: currentSettings.maxTokenBudget,
|
|
61
79
|
budgetWarningThreshold: currentSettings.budgetWarningThreshold,
|
|
62
80
|
autoCheckpointOnMilestone: currentSettings.autoCheckpointOnMilestone,
|
|
81
|
+
enableLiveActivityFeed: currentSettings.enableLiveActivityFeed,
|
|
82
|
+
enablePreplanning: currentSettings.enablePreplanning,
|
|
83
|
+
autoBranchOnGoalStart: currentSettings.autoBranchOnGoalStart,
|
|
84
|
+
enablePostGoalRetrospective: currentSettings.enablePostGoalRetrospective,
|
|
85
|
+
autoScaleBudgetNearCompletion: currentSettings.autoScaleBudgetNearCompletion,
|
|
86
|
+
enableTemplatesDrawer: currentSettings.enableTemplatesDrawer,
|
|
87
|
+
enableMilestoneDependencies: currentSettings.enableMilestoneDependencies,
|
|
88
|
+
soundScheme: currentSettings.soundScheme,
|
|
89
|
+
enableVoiceAnnouncements: currentSettings.enableVoiceAnnouncements,
|
|
63
90
|
storagePath,
|
|
64
91
|
});
|
|
65
92
|
|
|
@@ -175,35 +202,19 @@ export function apply(ctx, config = {}) {
|
|
|
175
202
|
const getConfig = () => {
|
|
176
203
|
if (settingsScope) {
|
|
177
204
|
const snap = typeof settingsScope.getSnapshot === 'function' ? settingsScope.getSnapshot() : null;
|
|
205
|
+
let live = null;
|
|
178
206
|
if (snap) {
|
|
179
|
-
if (snap.status === 'ready' || snap.status === undefined) {
|
|
180
|
-
|
|
181
|
-
if (live && typeof live === 'object') {
|
|
182
|
-
return {
|
|
183
|
-
maxIterations: typeof live.maxIterations === 'number' ? live.maxIterations : currentSettings.maxIterations,
|
|
184
|
-
autoDrive: typeof live.autoDrive === 'boolean' ? live.autoDrive : currentSettings.autoDrive,
|
|
185
|
-
enableSound: typeof live.enableSound === 'boolean' ? live.enableSound : currentSettings.enableSound,
|
|
186
|
-
showQuickLaunchButton: typeof live.showQuickLaunchButton === 'boolean' ? live.showQuickLaunchButton : currentSettings.showQuickLaunchButton,
|
|
187
|
-
consecutiveToolFailureLimit: typeof live.consecutiveToolFailureLimit === 'number' ? live.consecutiveToolFailureLimit : currentSettings.consecutiveToolFailureLimit,
|
|
188
|
-
enableBrowserNotifications: typeof live.enableBrowserNotifications === 'boolean' ? live.enableBrowserNotifications : currentSettings.enableBrowserNotifications,
|
|
189
|
-
maxTokenBudget: typeof live.maxTokenBudget === 'number' ? live.maxTokenBudget : currentSettings.maxTokenBudget,
|
|
190
|
-
budgetWarningThreshold: typeof live.budgetWarningThreshold === 'number' ? live.budgetWarningThreshold : currentSettings.budgetWarningThreshold,
|
|
191
|
-
autoCheckpointOnMilestone: typeof live.autoCheckpointOnMilestone === 'boolean' ? live.autoCheckpointOnMilestone : currentSettings.autoCheckpointOnMilestone,
|
|
192
|
-
storagePath: typeof live.storagePath === 'string' ? live.storagePath : currentSettings.storagePath,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
207
|
+
if (snap.status === 'ready' || snap.status === 'writable' || snap.status === undefined) {
|
|
208
|
+
live = snap.value || (typeof settingsScope.get === 'function' ? settingsScope.get() : null);
|
|
195
209
|
}
|
|
196
210
|
} else if (typeof settingsScope.get === 'function') {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
storagePath: typeof live.storagePath === 'string' ? live.storagePath : currentSettings.storagePath,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
211
|
+
live = settingsScope.get();
|
|
212
|
+
}
|
|
213
|
+
if (live && typeof live === 'object') {
|
|
214
|
+
return {
|
|
215
|
+
...currentSettings,
|
|
216
|
+
...live,
|
|
217
|
+
};
|
|
207
218
|
}
|
|
208
219
|
}
|
|
209
220
|
return currentSettings;
|
|
@@ -220,6 +231,15 @@ export function apply(ctx, config = {}) {
|
|
|
220
231
|
maxTokenBudget: live.maxTokenBudget,
|
|
221
232
|
budgetWarningThreshold: live.budgetWarningThreshold,
|
|
222
233
|
autoCheckpointOnMilestone: live.autoCheckpointOnMilestone,
|
|
234
|
+
enableLiveActivityFeed: live.enableLiveActivityFeed,
|
|
235
|
+
enablePreplanning: live.enablePreplanning,
|
|
236
|
+
autoBranchOnGoalStart: live.autoBranchOnGoalStart,
|
|
237
|
+
enablePostGoalRetrospective: live.enablePostGoalRetrospective,
|
|
238
|
+
autoScaleBudgetNearCompletion: live.autoScaleBudgetNearCompletion,
|
|
239
|
+
enableTemplatesDrawer: live.enableTemplatesDrawer,
|
|
240
|
+
enableMilestoneDependencies: live.enableMilestoneDependencies,
|
|
241
|
+
soundScheme: live.soundScheme,
|
|
242
|
+
enableVoiceAnnouncements: live.enableVoiceAnnouncements,
|
|
223
243
|
});
|
|
224
244
|
};
|
|
225
245
|
|
|
@@ -280,7 +300,9 @@ export function apply(ctx, config = {}) {
|
|
|
280
300
|
try {
|
|
281
301
|
if (typeof pctx.systemPrompt?.section === 'function') {
|
|
282
302
|
const globalSections = pctx.systemPrompt.layers?.global?.sections;
|
|
303
|
+
let displacedPromptSection = null;
|
|
283
304
|
if (globalSections?.data instanceof Map && globalSections.data.has('tool:goal')) {
|
|
305
|
+
displacedPromptSection = globalSections.data.get('tool:goal');
|
|
284
306
|
globalSections.data.delete('tool:goal');
|
|
285
307
|
}
|
|
286
308
|
|
|
@@ -298,7 +320,12 @@ export function apply(ctx, config = {}) {
|
|
|
298
320
|
});
|
|
299
321
|
|
|
300
322
|
if (typeof unregisterSection === 'function') {
|
|
301
|
-
pctx.effect(() => () =>
|
|
323
|
+
pctx.effect(() => () => {
|
|
324
|
+
unregisterSection();
|
|
325
|
+
if (displacedPromptSection && globalSections?.data instanceof Map) {
|
|
326
|
+
globalSections.data.set('tool:goal', displacedPromptSection);
|
|
327
|
+
}
|
|
328
|
+
}, 'dsh-goal: system prompt section');
|
|
302
329
|
}
|
|
303
330
|
}
|
|
304
331
|
} catch (err) {
|
package/lib/routes.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { MilestoneStatus, detectLanguage, sessionIdOf } from './goal-engine-constants.js';
|
|
4
4
|
import { saveGoalArtifact, rollbackToCheckpoint } from './engine-reports.js';
|
|
5
|
+
import { getTemplatesCatalogue } from './engine-templates.js';
|
|
5
6
|
|
|
6
7
|
export function isLoopback(value) {
|
|
7
8
|
if (!value) return false;
|
|
@@ -20,10 +21,6 @@ export function isTrustedCaller(req) {
|
|
|
20
21
|
const remote = req.socket?.remoteAddress || req.connection?.remoteAddress || '';
|
|
21
22
|
if (isLoopback(remote)) return true;
|
|
22
23
|
|
|
23
|
-
const secSite = String(req.headers?.['sec-fetch-site'] || '').toLowerCase();
|
|
24
|
-
if (secSite === 'same-origin' || secSite === 'same-site' || secSite === 'none') return true;
|
|
25
|
-
if (secSite === 'cross-site') return false;
|
|
26
|
-
|
|
27
24
|
const host = String(req.headers?.host || '').toLowerCase();
|
|
28
25
|
const origin = String(req.headers?.origin || '').trim().toLowerCase();
|
|
29
26
|
if (origin) {
|
|
@@ -166,6 +163,11 @@ export function registerRoutes(ctx, {
|
|
|
166
163
|
|
|
167
164
|
// GET /dsh-goal/events: Server-Sent Events realtime snapshot stream
|
|
168
165
|
if (req.method === 'GET' && (pathname === '/dsh-goal/events' || pathname === '/dsh-goal/events/')) {
|
|
166
|
+
if (!isTrustedCaller(req)) {
|
|
167
|
+
res.statusCode = 403;
|
|
168
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
169
|
+
return res.end(JSON.stringify({ error: 'Forbidden: untrusted caller origin' }));
|
|
170
|
+
}
|
|
169
171
|
const sid = sessionIdOf(req, 'default');
|
|
170
172
|
res.writeHead(200, {
|
|
171
173
|
'Content-Type': 'text/event-stream',
|
|
@@ -194,8 +196,22 @@ export function registerRoutes(ctx, {
|
|
|
194
196
|
|
|
195
197
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
196
198
|
|
|
199
|
+
// GET /dsh-goal/templates
|
|
200
|
+
if (req.method === 'GET' && (pathname === '/dsh-goal/templates' || pathname === '/dsh-goal/templates/')) {
|
|
201
|
+
if (!isTrustedCaller(req)) {
|
|
202
|
+
res.statusCode = 403;
|
|
203
|
+
return res.end(JSON.stringify({ error: 'Forbidden: untrusted caller origin' }));
|
|
204
|
+
}
|
|
205
|
+
res.statusCode = 200;
|
|
206
|
+
return res.end(JSON.stringify({ ok: true, templates: getTemplatesCatalogue() }));
|
|
207
|
+
}
|
|
208
|
+
|
|
197
209
|
// GET /dsh-goal/state
|
|
198
210
|
if (req.method === 'GET' && (pathname === '/dsh-goal/state' || pathname === '/dsh-goal/state/')) {
|
|
211
|
+
if (!isTrustedCaller(req)) {
|
|
212
|
+
res.statusCode = 403;
|
|
213
|
+
return res.end(JSON.stringify({ error: 'Forbidden: untrusted caller origin' }));
|
|
214
|
+
}
|
|
199
215
|
const sid = sessionIdOf(req, 'default');
|
|
200
216
|
res.statusCode = 200;
|
|
201
217
|
return res.end(JSON.stringify(engine.getSnapshot(sid)));
|
|
@@ -261,13 +277,26 @@ export function registerRoutes(ctx, {
|
|
|
261
277
|
res.statusCode = 400;
|
|
262
278
|
return res.end(JSON.stringify({ error: 'Goal title cannot be empty' }));
|
|
263
279
|
}
|
|
280
|
+
const trustedRoots = [process.cwd()];
|
|
281
|
+
if (process.env.DSH_WORKSPACE_ROOT) {
|
|
282
|
+
trustedRoots.push(process.env.DSH_WORKSPACE_ROOT);
|
|
283
|
+
}
|
|
284
|
+
let safeStartCwd = undefined;
|
|
285
|
+
if (data.cwd) {
|
|
286
|
+
const validated = getSafeCwd(data.cwd, trustedRoots);
|
|
287
|
+
if (!validated) {
|
|
288
|
+
res.statusCode = 400;
|
|
289
|
+
return res.end(JSON.stringify({ error: 'Invalid or unauthorized working directory' }));
|
|
290
|
+
}
|
|
291
|
+
safeStartCwd = validated;
|
|
292
|
+
}
|
|
264
293
|
const detectedLang = data.lang || detectLanguage(cleanTitle);
|
|
265
294
|
const conf = typeof getConfig === 'function' ? getConfig() : { maxIterations: 25 };
|
|
266
295
|
result = engine.startGoal(cleanTitle, {
|
|
267
296
|
description: typeof description === 'string' ? description.trim() : '',
|
|
268
297
|
maxIterations: conf.maxIterations || 25,
|
|
269
298
|
lang: detectedLang,
|
|
270
|
-
cwd:
|
|
299
|
+
cwd: safeStartCwd,
|
|
271
300
|
}, sid);
|
|
272
301
|
|
|
273
302
|
const startPrompt = detectedLang === 'zh'
|
|
@@ -383,7 +412,15 @@ export function registerRoutes(ctx, {
|
|
|
383
412
|
}
|
|
384
413
|
const sessionGoal = engine.getGoal(sid);
|
|
385
414
|
const allowedRoots = [process.cwd()];
|
|
386
|
-
if (
|
|
415
|
+
if (process.env.DSH_WORKSPACE_ROOT) {
|
|
416
|
+
allowedRoots.push(process.env.DSH_WORKSPACE_ROOT);
|
|
417
|
+
}
|
|
418
|
+
if (sessionGoal?.cwd) {
|
|
419
|
+
const isCwdAllowed = allowedRoots.some((root) => isPathInsideOrEqual(root, sessionGoal.cwd));
|
|
420
|
+
if (isCwdAllowed) {
|
|
421
|
+
allowedRoots.push(sessionGoal.cwd);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
387
424
|
const safeCwd = getSafeCwd(data.cwd, allowedRoots);
|
|
388
425
|
if (!safeCwd) {
|
|
389
426
|
res.statusCode = 400;
|
|
@@ -406,7 +443,15 @@ export function registerRoutes(ctx, {
|
|
|
406
443
|
}
|
|
407
444
|
const sessionGoal = engine.getGoal(sid);
|
|
408
445
|
const allowedRoots = [process.cwd()];
|
|
409
|
-
if (
|
|
446
|
+
if (process.env.DSH_WORKSPACE_ROOT) {
|
|
447
|
+
allowedRoots.push(process.env.DSH_WORKSPACE_ROOT);
|
|
448
|
+
}
|
|
449
|
+
if (sessionGoal?.cwd) {
|
|
450
|
+
const isCwdAllowed = allowedRoots.some((root) => isPathInsideOrEqual(root, sessionGoal.cwd));
|
|
451
|
+
if (isCwdAllowed) {
|
|
452
|
+
allowedRoots.push(sessionGoal.cwd);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
410
455
|
const safeCwd = getSafeCwd(data.cwd, allowedRoots);
|
|
411
456
|
if (!safeCwd) {
|
|
412
457
|
res.statusCode = 400;
|
|
@@ -421,6 +466,51 @@ export function registerRoutes(ctx, {
|
|
|
421
466
|
return res.end(JSON.stringify({ ok: true, artifact, state: snap }));
|
|
422
467
|
}
|
|
423
468
|
|
|
469
|
+
case 'intervene': {
|
|
470
|
+
result = engine.intervene(data, sid);
|
|
471
|
+
if (data.resume) {
|
|
472
|
+
engine.resume(sid);
|
|
473
|
+
const prompt = engine.getStatePromptInjection(sid);
|
|
474
|
+
if (typeof resumeActiveAgent === 'function') {
|
|
475
|
+
resumeActiveAgent(prompt, sid);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
case 'preplan_approve': {
|
|
482
|
+
result = engine.approvePreplan(sid);
|
|
483
|
+
const prompt = engine.getStatePromptInjection(sid);
|
|
484
|
+
if (typeof resumeActiveAgent === 'function') {
|
|
485
|
+
resumeActiveAgent(prompt, sid);
|
|
486
|
+
}
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
case 'branch_action': {
|
|
491
|
+
const op = String(data.operation || data.branchOperation || 'merge').toLowerCase();
|
|
492
|
+
const targetBranch = String(data.targetBranch || 'main');
|
|
493
|
+
if (op === 'merge') {
|
|
494
|
+
const ok = engine.mergeGoalBranch(targetBranch, sid);
|
|
495
|
+
if (!ok) {
|
|
496
|
+
res.statusCode = 500;
|
|
497
|
+
return res.end(JSON.stringify({ error: `Failed to merge goal branch into ${targetBranch}` }));
|
|
498
|
+
}
|
|
499
|
+
} else if (op === 'discard') {
|
|
500
|
+
const ok = engine.discardGoalBranch(targetBranch, sid);
|
|
501
|
+
if (!ok) {
|
|
502
|
+
res.statusCode = 500;
|
|
503
|
+
return res.end(JSON.stringify({ error: 'Failed to discard goal branch' }));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
result = engine.getSnapshot(sid);
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
case 'templates':
|
|
511
|
+
res.statusCode = 200;
|
|
512
|
+
return res.end(JSON.stringify({ ok: true, templates: getTemplatesCatalogue() }));
|
|
513
|
+
|
|
424
514
|
default:
|
|
425
515
|
res.statusCode = 400;
|
|
426
516
|
return res.end(JSON.stringify({ error: `Unknown action: ${action}` }));
|
package/lib/tools.js
CHANGED
|
@@ -46,16 +46,23 @@ export function registerTools(ctx, { engine, resumeActiveAgent }) {
|
|
|
46
46
|
ctx.inject(['tools'], (tctx) => {
|
|
47
47
|
if (!tctx.tools?.register) return;
|
|
48
48
|
|
|
49
|
+
const displacedTools = new Map();
|
|
49
50
|
const safeRegister = (definition) => {
|
|
50
51
|
try {
|
|
51
52
|
const name = definition.name;
|
|
52
53
|
const globalTools = tctx.tools.layers?.global?.tools;
|
|
53
54
|
if (globalTools?.data instanceof Map && globalTools.data.has(name)) {
|
|
55
|
+
displacedTools.set(name, globalTools.data.get(name));
|
|
54
56
|
globalTools.data.delete(name);
|
|
55
57
|
}
|
|
56
58
|
const unregister = tctx.tools.register(definition);
|
|
57
59
|
if (typeof unregister === 'function') {
|
|
58
|
-
tctx.effect(() => () =>
|
|
60
|
+
tctx.effect(() => () => {
|
|
61
|
+
unregister();
|
|
62
|
+
if (displacedTools.has(name) && globalTools?.data instanceof Map) {
|
|
63
|
+
globalTools.data.set(name, displacedTools.get(name));
|
|
64
|
+
}
|
|
65
|
+
}, `dsh-goal: tool ${name}`);
|
|
59
66
|
}
|
|
60
67
|
} catch (err) {
|
|
61
68
|
console.warn(`[dsh-goal] Tool ${definition.name} registration skipped:`, err.message);
|
|
@@ -195,6 +202,9 @@ export function registerTools(ctx, { engine, resumeActiveAgent }) {
|
|
|
195
202
|
const handleSetMilestones = async ({ milestones }, toolCtx) => {
|
|
196
203
|
const sid = sessionIdOf(toolCtx, 'default');
|
|
197
204
|
const snap = engine.setMilestones(milestones, sid);
|
|
205
|
+
if (typeof engine.recordToolActivity === 'function') {
|
|
206
|
+
engine.recordToolActivity('goal_set_milestones', 'success', 'Defined milestones', sid);
|
|
207
|
+
}
|
|
198
208
|
return {
|
|
199
209
|
success: true,
|
|
200
210
|
count: snap.milestones?.length || 0,
|
|
@@ -215,6 +225,7 @@ export function registerTools(ctx, { engine, resumeActiveAgent }) {
|
|
|
215
225
|
properties: {
|
|
216
226
|
id: { type: 'string', description: 'Short identifier, e.g. M1, step-1' },
|
|
217
227
|
title: { type: 'string', description: 'Concise actionable title' },
|
|
228
|
+
dependsOn: { type: 'array', items: { type: 'string' }, description: 'Prerequisite milestone IDs' },
|
|
218
229
|
},
|
|
219
230
|
required: ['id', 'title'],
|
|
220
231
|
},
|
|
@@ -231,7 +242,10 @@ export function registerTools(ctx, { engine, resumeActiveAgent }) {
|
|
|
231
242
|
// Tool 5: goal_update_progress
|
|
232
243
|
const handleUpdateProgress = async ({ milestone_id, status, notes, checklist }, toolCtx) => {
|
|
233
244
|
const sid = sessionIdOf(toolCtx, 'default');
|
|
234
|
-
engine.updateMilestone(milestone_id, status, notes, sid, checklist);
|
|
245
|
+
const ok = engine.updateMilestone(milestone_id, status, notes, sid, checklist);
|
|
246
|
+
if (typeof engine.recordToolActivity === 'function') {
|
|
247
|
+
engine.recordToolActivity('goal_update_progress', ok ? 'success' : 'error', String(milestone_id) + ' -> ' + String(status), sid);
|
|
248
|
+
}
|
|
235
249
|
const snap = engine.getSnapshot(sid);
|
|
236
250
|
const targetMilestone = snap.milestones?.find((m) => m.id === String(milestone_id) || m.id.toLowerCase().replace(/[^a-z0-9]/g, '') === String(milestone_id).toLowerCase().replace(/[^a-z0-9]/g, '') || m.id.toLowerCase().replace(/[^0-9]/g, '') === String(milestone_id).toLowerCase().replace(/[^0-9]/g, ''));
|
|
237
251
|
return {
|
|
@@ -282,6 +296,9 @@ export function registerTools(ctx, { engine, resumeActiveAgent }) {
|
|
|
282
296
|
const handleGoalFinish = async ({ summary }, toolCtx) => {
|
|
283
297
|
const sid = sessionIdOf(toolCtx, 'default');
|
|
284
298
|
const snap = engine.completeGoal(summary, sid);
|
|
299
|
+
if (typeof engine.recordToolActivity === 'function') {
|
|
300
|
+
engine.recordToolActivity('goal_finish', 'success', summary ? summary.slice(0, 80) : 'Goal finished', sid);
|
|
301
|
+
}
|
|
285
302
|
return {
|
|
286
303
|
success: true,
|
|
287
304
|
completed: true,
|
package/package.json
CHANGED