@goodandready/dsh-goal 0.2.29 → 0.2.31
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 +685 -32
- package/lib/engine-diff.js +147 -0
- package/lib/engine-kanban-sync.js +242 -0
- package/lib/engine-operator-query.js +161 -0
- package/lib/engine-prompt.js +12 -0
- package/lib/engine-templates.js +15 -2
- package/lib/goal-engine-constants.js +4 -0
- package/lib/goal-engine.js +52 -49
- package/lib/routes.js +70 -1
- package/lib/tools.js +65 -0
- package/package.json +1 -1
package/lib/goal-engine.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
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';
|
|
4
|
+
import { askOperatorQuery, answerOperatorQuery } from './engine-operator-query.js';
|
|
2
5
|
import { GoalMode, GoalState, MilestoneStatus, formatElapsed, formatETA, detectLanguage, sessionIdOf, calculateElapsedSeconds, calculateRemainingSeconds, buildSnapshot, getSafeCwd } from './goal-engine-constants.js';
|
|
3
6
|
import { getGitCurrentCommit, exportReportMarkdown, exportReportGitHubPR, createMilestoneCheckpoint, rollbackToCheckpoint, saveGoalArtifact } from './engine-reports.js';
|
|
4
7
|
import { EngineStore } from './engine-store.js';
|
|
@@ -11,8 +14,7 @@ import { generateRetrospectiveData } from './engine-retrospective.js';
|
|
|
11
14
|
import { generatePreplanDraft } from './engine-templates.js';
|
|
12
15
|
import { pruneInactiveSessions, addNudgeToGoal, consumeGoalNudge, extendGoalBudget, setGoalMode, pauseGoal, resumeGoal, cancelGoal } from './engine-sessions.js';
|
|
13
16
|
|
|
14
|
-
export { GoalMode, GoalState, MilestoneStatus, formatElapsed, formatETA, detectLanguage, sessionIdOf, matchMilestone, parseIssueChecklist };
|
|
15
|
-
export { getGitCurrentCommit, exportReportMarkdown, exportReportGitHubPR, createMilestoneCheckpoint, rollbackToCheckpoint, saveGoalArtifact };
|
|
17
|
+
export { GoalMode, GoalState, MilestoneStatus, formatElapsed, formatETA, detectLanguage, sessionIdOf, matchMilestone, parseIssueChecklist, getGitCurrentCommit, exportReportMarkdown, exportReportGitHubPR, createMilestoneCheckpoint, rollbackToCheckpoint, saveGoalArtifact };
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* Isolated Goal Management Engine for DeepSeek Harness
|
|
@@ -22,25 +24,19 @@ export class GoalEngine {
|
|
|
22
24
|
this.logger = options.logger || console;
|
|
23
25
|
const rawDefIters = options.defaultMaxIterations ?? 25;
|
|
24
26
|
this.defaultMaxIterations = typeof rawDefIters === "number" ? rawDefIters : (typeof rawDefIters?.value === "number" ? rawDefIters.value : 25);
|
|
25
|
-
this
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this.enableMilestoneDependencies = options.enableMilestoneDependencies ?? true;
|
|
39
|
-
this.soundScheme = options.soundScheme ?? 'default';
|
|
40
|
-
this.enableVoiceAnnouncements = options.enableVoiceAnnouncements ?? false;
|
|
41
|
-
this.approvalGate = options.approvalGate || null;
|
|
42
|
-
this.checkApproval = options.checkApproval || null;
|
|
43
|
-
this.maxSessions = options.maxSessions ?? 100;
|
|
27
|
+
Object.assign(this, {
|
|
28
|
+
autoDrive: options.autoDrive ?? true, enableSound: options.enableSound ?? true,
|
|
29
|
+
showQuickLaunchButton: options.showQuickLaunchButton ?? true,
|
|
30
|
+
consecutiveToolFailureLimit: options.consecutiveToolFailureLimit ?? 3,
|
|
31
|
+
maxTokenBudget: options.maxTokenBudget ?? 0, budgetWarningThreshold: options.budgetWarningThreshold ?? 80,
|
|
32
|
+
autoCheckpointOnMilestone: options.autoCheckpointOnMilestone ?? false,
|
|
33
|
+
enableLiveActivityFeed: options.enableLiveActivityFeed ?? true, enablePreplanning: options.enablePreplanning ?? true,
|
|
34
|
+
autoBranchOnGoalStart: options.autoBranchOnGoalStart ?? true, enablePostGoalRetrospective: options.enablePostGoalRetrospective ?? true,
|
|
35
|
+
autoScaleBudgetNearCompletion: options.autoScaleBudgetNearCompletion ?? false, enableTemplatesDrawer: options.enableTemplatesDrawer ?? true,
|
|
36
|
+
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,
|
|
39
|
+
});
|
|
44
40
|
this.goals = new Map();
|
|
45
41
|
this.listeners = new Set();
|
|
46
42
|
this.stallCounters = new Map();
|
|
@@ -54,10 +50,7 @@ export class GoalEngine {
|
|
|
54
50
|
set storagePath(val) { this.store.setStoragePath(val); }
|
|
55
51
|
get saveTimer() { return this.store.saveTimer; }
|
|
56
52
|
get currentGoal() { return this.goals.get('default') || null; }
|
|
57
|
-
set currentGoal(val) {
|
|
58
|
-
if (val) this.goals.set('default', val);
|
|
59
|
-
else this.goals.delete('default');
|
|
60
|
-
}
|
|
53
|
+
set currentGoal(val) { if (val) this.goals.set('default', val); else this.goals.delete('default'); }
|
|
61
54
|
|
|
62
55
|
loadStateFromDisk() { this.store.loadStateFromDisk(); }
|
|
63
56
|
scheduleSave(immediate = false) { this.store.scheduleSave(immediate); }
|
|
@@ -111,22 +104,12 @@ export class GoalEngine {
|
|
|
111
104
|
}
|
|
112
105
|
}
|
|
113
106
|
}
|
|
114
|
-
|
|
115
|
-
if (typeof config
|
|
116
|
-
if (typeof config.showQuickLaunchButton === 'boolean') this.showQuickLaunchButton = config.showQuickLaunchButton;
|
|
107
|
+
const boolKeys = ['autoDrive', 'enableSound', 'showQuickLaunchButton', 'autoCheckpointOnMilestone', 'enableLiveActivityFeed', 'enablePreplanning', 'autoBranchOnGoalStart', 'enablePostGoalRetrospective', 'autoScaleBudgetNearCompletion', 'enableTemplatesDrawer', 'enableMilestoneDependencies', 'enableVoiceAnnouncements'];
|
|
108
|
+
for (const k of boolKeys) if (typeof config[k] === 'boolean') this[k] = config[k];
|
|
117
109
|
if (typeof config.consecutiveToolFailureLimit === 'number') this.consecutiveToolFailureLimit = Math.max(0, config.consecutiveToolFailureLimit);
|
|
118
110
|
if (typeof config.maxTokenBudget === 'number') this.maxTokenBudget = Math.max(0, config.maxTokenBudget);
|
|
119
111
|
if (typeof config.budgetWarningThreshold === 'number') this.budgetWarningThreshold = Math.max(1, Math.min(100, config.budgetWarningThreshold));
|
|
120
|
-
if (typeof config.autoCheckpointOnMilestone === 'boolean') this.autoCheckpointOnMilestone = config.autoCheckpointOnMilestone;
|
|
121
|
-
if (typeof config.enableLiveActivityFeed === 'boolean') this.enableLiveActivityFeed = config.enableLiveActivityFeed;
|
|
122
|
-
if (typeof config.enablePreplanning === 'boolean') this.enablePreplanning = config.enablePreplanning;
|
|
123
|
-
if (typeof config.autoBranchOnGoalStart === 'boolean') this.autoBranchOnGoalStart = config.autoBranchOnGoalStart;
|
|
124
|
-
if (typeof config.enablePostGoalRetrospective === 'boolean') this.enablePostGoalRetrospective = config.enablePostGoalRetrospective;
|
|
125
|
-
if (typeof config.autoScaleBudgetNearCompletion === 'boolean') this.autoScaleBudgetNearCompletion = config.autoScaleBudgetNearCompletion;
|
|
126
|
-
if (typeof config.enableTemplatesDrawer === 'boolean') this.enableTemplatesDrawer = config.enableTemplatesDrawer;
|
|
127
|
-
if (typeof config.enableMilestoneDependencies === 'boolean') this.enableMilestoneDependencies = config.enableMilestoneDependencies;
|
|
128
112
|
if (typeof config.soundScheme === 'string') this.soundScheme = config.soundScheme;
|
|
129
|
-
if (typeof config.enableVoiceAnnouncements === 'boolean') this.enableVoiceAnnouncements = config.enableVoiceAnnouncements;
|
|
130
113
|
this.emit();
|
|
131
114
|
}
|
|
132
115
|
|
|
@@ -140,14 +123,8 @@ export class GoalEngine {
|
|
|
140
123
|
this.toolFailureCounters.set(sid, next);
|
|
141
124
|
return next;
|
|
142
125
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
this.toolFailureCounters.set(sessionId || 'default', 0);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
getToolFailureCount(sessionId = 'default') {
|
|
149
|
-
return this.toolFailureCounters.get(sessionId || 'default') || 0;
|
|
150
|
-
}
|
|
126
|
+
resetToolFailureCount(sessionId = 'default') { this.toolFailureCounters.set(sessionId || 'default', 0); }
|
|
127
|
+
getToolFailureCount(sessionId = 'default') { return this.toolFailureCounters.get(sessionId || 'default') || 0; }
|
|
151
128
|
|
|
152
129
|
nudge(text, sessionId = 'default') {
|
|
153
130
|
const sid = sessionId || 'default';
|
|
@@ -205,7 +182,7 @@ export class GoalEngine {
|
|
|
205
182
|
title: cleanTitle,
|
|
206
183
|
description: options.description?.trim() || '',
|
|
207
184
|
lang: options.lang || detectLanguage(cleanTitle),
|
|
208
|
-
cwd: options.cwd ? getSafeCwd(options.cwd) : null,
|
|
185
|
+
cwd: options.cwd ? getSafeCwd(options.cwd, options.allowedRoots || this.allowedRoots || []) : null,
|
|
209
186
|
mode: initialMode,
|
|
210
187
|
state: initialState,
|
|
211
188
|
startedAt: now,
|
|
@@ -218,7 +195,7 @@ export class GoalEngine {
|
|
|
218
195
|
budgetWarningTriggered: false,
|
|
219
196
|
budgetAutoScaled: false,
|
|
220
197
|
gitStartCommit: gitCommit || null,
|
|
221
|
-
gitBranch: null,
|
|
198
|
+
gitBranch: options.gitBranch || options.branchName || null,
|
|
222
199
|
gitParentBranch: null,
|
|
223
200
|
pendingNudge: null,
|
|
224
201
|
nudges: [],
|
|
@@ -229,6 +206,8 @@ export class GoalEngine {
|
|
|
229
206
|
preplan: null,
|
|
230
207
|
retrospective: null,
|
|
231
208
|
issueRef: parseIssueRef(cleanTitle),
|
|
209
|
+
operatorQuery: null,
|
|
210
|
+
waitingForOperator: false,
|
|
232
211
|
tokensUsage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
233
212
|
logs: [{
|
|
234
213
|
timestamp: now,
|
|
@@ -338,7 +317,8 @@ export class GoalEngine {
|
|
|
338
317
|
}
|
|
339
318
|
|
|
340
319
|
addMilestones(milestonesList, shouldEmit = true, sessionId = 'default') {
|
|
341
|
-
const sid = sessionId || 'default';
|
|
320
|
+
const sid = typeof shouldEmit === 'string' ? shouldEmit : (sessionId || 'default');
|
|
321
|
+
const shouldEmitFlag = typeof shouldEmit === 'boolean' ? shouldEmit : true;
|
|
342
322
|
const goal = this.goals.get(sid);
|
|
343
323
|
if (!goal || !Array.isArray(milestonesList)) return [];
|
|
344
324
|
|
|
@@ -348,7 +328,7 @@ export class GoalEngine {
|
|
|
348
328
|
}
|
|
349
329
|
this.recordProgress(sid);
|
|
350
330
|
recordActivity(goal, { type: 'milestone', tool: 'addMilestones', status: 'success', details: `Added ${newMilestones.length} milestones` });
|
|
351
|
-
if (
|
|
331
|
+
if (shouldEmitFlag) this.emit(sid);
|
|
352
332
|
return newMilestones;
|
|
353
333
|
}
|
|
354
334
|
|
|
@@ -382,6 +362,7 @@ export class GoalEngine {
|
|
|
382
362
|
|
|
383
363
|
const prevStatus = res.prevStatus;
|
|
384
364
|
recordActivity(goal, { type: 'milestone', tool: 'updateMilestone', status: 'success', details: `${id} -> ${status}` });
|
|
365
|
+
if (this.enableKanbanSync) syncMilestoneStatusToKanban(target, { goal, ctx: this.ctx, store: this.kanbanStore, kanbanService: this.kanbanService }).catch(() => {});
|
|
385
366
|
|
|
386
367
|
if (target.status === MilestoneStatus.COMPLETED && prevStatus !== MilestoneStatus.COMPLETED) {
|
|
387
368
|
if (this.autoCheckpointOnMilestone) {
|
|
@@ -584,6 +565,28 @@ export class GoalEngine {
|
|
|
584
565
|
return this.goals.get(sessionId || 'default') || null;
|
|
585
566
|
}
|
|
586
567
|
|
|
568
|
+
askOperator(query = {}, sessionId = 'default') {
|
|
569
|
+
const sid = sessionId || 'default';
|
|
570
|
+
const goal = this.goals.get(sid);
|
|
571
|
+
if (!goal) return { ok: false, error: 'Goal not found' };
|
|
572
|
+
const res = askOperatorQuery(goal, query);
|
|
573
|
+
this.emit(sid, true);
|
|
574
|
+
return res;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
answerOperator(payload = {}, sessionId = 'default') {
|
|
578
|
+
const sid = sessionId || 'default';
|
|
579
|
+
const goal = this.goals.get(sid);
|
|
580
|
+
if (!goal) return { ok: false, error: 'Goal not found' };
|
|
581
|
+
const res = answerOperatorQuery(goal, payload);
|
|
582
|
+
this.emit(sid, true);
|
|
583
|
+
return res;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
getDiff(sessionId = 'default', options = {}) { return getEngineGoalDiff(this, sessionId, options); }
|
|
587
|
+
syncKanban(sessionId = 'default', options = {}) { return syncGoalToKanban(this, sessionId, options); }
|
|
588
|
+
handleKanbanCardMove(payload = {}, sessionId = 'default') { return handleKanbanCardMoved(this, payload, sessionId); }
|
|
589
|
+
|
|
587
590
|
isPlanMode(sessionId = 'default') {
|
|
588
591
|
const goal = this.goals.get(sessionId || 'default');
|
|
589
592
|
return Boolean(goal && (goal.mode === GoalMode.PLAN || goal.state === GoalState.PLANNING));
|
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 || '{}');
|
|
@@ -750,6 +772,30 @@ export function registerRoutes(ctx, {
|
|
|
750
772
|
break;
|
|
751
773
|
}
|
|
752
774
|
|
|
775
|
+
case 'answer_operator': {
|
|
776
|
+
const answer = typeof data.answer === 'string' ? data.answer.trim() : '';
|
|
777
|
+
const optionIndex = typeof data.optionIndex === 'number' ? data.optionIndex : (typeof data.option_index === 'number' ? data.option_index : null);
|
|
778
|
+
if (engine && typeof engine.answerOperator === 'function') {
|
|
779
|
+
const ansRes = engine.answerOperator({ answer, optionIndex }, sid);
|
|
780
|
+
if (!ansRes.ok) {
|
|
781
|
+
res.statusCode = 400;
|
|
782
|
+
return res.end(JSON.stringify({ error: ansRes.error || 'Failed to submit operator answer' }));
|
|
783
|
+
}
|
|
784
|
+
result = { ok: true, answer: ansRes.answer, state: engine.getSnapshot(sid) };
|
|
785
|
+
const snap = engine.getSnapshot(sid);
|
|
786
|
+
const detectedLang = snap?.lang || 'en';
|
|
787
|
+
const prompt = detectedLang === 'zh'
|
|
788
|
+
? `✅ 操作员已答复你的提问: "${ansRes.answer}"。请继续执行任务。`
|
|
789
|
+
: `✅ Operator answered your query: "${ansRes.answer}". Proceed with task execution.`;
|
|
790
|
+
if (typeof resumeActiveAgent === 'function') {
|
|
791
|
+
resumeActiveAgent(prompt, sid);
|
|
792
|
+
}
|
|
793
|
+
} else {
|
|
794
|
+
result = { ok: true, answer };
|
|
795
|
+
}
|
|
796
|
+
break;
|
|
797
|
+
}
|
|
798
|
+
|
|
753
799
|
case 'approve_plan': {
|
|
754
800
|
if (engine) {
|
|
755
801
|
const snap = engine.getSnapshot(sid);
|
|
@@ -805,6 +851,28 @@ export function registerRoutes(ctx, {
|
|
|
805
851
|
res.statusCode = 200;
|
|
806
852
|
return res.end(JSON.stringify({ ok: true, templates: getTemplatesCatalogue() }));
|
|
807
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
|
+
|
|
808
876
|
case 'instantiate_template': {
|
|
809
877
|
const tplId = data.templateId || data.id;
|
|
810
878
|
if (!tplId) {
|
|
@@ -846,6 +914,7 @@ export function registerRoutes(ctx, {
|
|
|
846
914
|
'/dsh-goal/events': ['GET'],
|
|
847
915
|
'/dsh-goal/templates': ['GET'],
|
|
848
916
|
'/dsh-goal/state': ['GET'],
|
|
917
|
+
'/dsh-goal/diff': ['GET'],
|
|
849
918
|
'/dsh-goal/action': ['POST'],
|
|
850
919
|
};
|
|
851
920
|
const cleanPath = pathname.replace(/\/+$/, '') || '/';
|
package/lib/tools.js
CHANGED
|
@@ -311,5 +311,70 @@ export function registerTools(ctx, { milestoneManager, goalsService, logger = co
|
|
|
311
311
|
output: JSON_OUTPUT,
|
|
312
312
|
execute: handleGoalFinish,
|
|
313
313
|
});
|
|
314
|
+
|
|
315
|
+
// Tool 6: goal_ask_operator (#166)
|
|
316
|
+
const handleGoalAskOperator = async (args = {}, toolCtx = {}) => {
|
|
317
|
+
const goalId = args.goalId || args.goal_id || resolveGoalId(toolCtx);
|
|
318
|
+
const question = args.question || args.prompt;
|
|
319
|
+
if (!question || typeof question !== 'string' || !question.trim()) {
|
|
320
|
+
throw new Error('Field "question" is required and must be a non-empty string');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const options = Array.isArray(args.options) ? args.options : [];
|
|
324
|
+
const isBlocking = args.isBlocking !== false && args.is_blocking !== false;
|
|
325
|
+
|
|
326
|
+
const engine = ctx.goalEngine || (ctx.get && ctx.get('goalEngine'));
|
|
327
|
+
if (engine && typeof engine.askOperator === 'function') {
|
|
328
|
+
const res = engine.askOperator({ question, options, isBlocking }, goalId);
|
|
329
|
+
if (!res.ok) {
|
|
330
|
+
throw new Error(`Failed to present question to operator: ${res.error}`);
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
ok: true,
|
|
334
|
+
goalId,
|
|
335
|
+
queryId: res.queryId,
|
|
336
|
+
question: res.question,
|
|
337
|
+
options: res.options,
|
|
338
|
+
isBlocking: res.isBlocking,
|
|
339
|
+
message: isBlocking
|
|
340
|
+
? 'Question submitted to human operator. Goal is paused awaiting operator input.'
|
|
341
|
+
: 'Question submitted to human operator in non-blocking mode.',
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
ok: true,
|
|
347
|
+
goalId,
|
|
348
|
+
question,
|
|
349
|
+
options,
|
|
350
|
+
message: 'Question logged.',
|
|
351
|
+
};
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
safeRegister({
|
|
355
|
+
name: 'goal_ask_operator',
|
|
356
|
+
description: 'Ask the human operator a clarification question or present architectural choices when encountering blockers (#166). Pauses autonomous execution without consuming iterations until the operator responds.',
|
|
357
|
+
parameters: {
|
|
358
|
+
type: 'object',
|
|
359
|
+
properties: {
|
|
360
|
+
question: {
|
|
361
|
+
type: 'string',
|
|
362
|
+
description: 'The clarification question, design decision, or blocker to ask the human operator',
|
|
363
|
+
},
|
|
364
|
+
options: {
|
|
365
|
+
type: 'array',
|
|
366
|
+
items: { type: 'string' },
|
|
367
|
+
description: 'Optional list of predefined choices or alternative options for the operator to select with one click',
|
|
368
|
+
},
|
|
369
|
+
is_blocking: {
|
|
370
|
+
type: 'boolean',
|
|
371
|
+
description: 'Whether to pause goal execution awaiting operator response (default: true)',
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
required: ['question'],
|
|
375
|
+
},
|
|
376
|
+
output: JSON_OUTPUT,
|
|
377
|
+
execute: handleGoalAskOperator,
|
|
378
|
+
});
|
|
314
379
|
});
|
|
315
380
|
}
|
package/package.json
CHANGED