@goodandready/dsh-goal 0.2.12 → 0.2.13
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/addon-store.js +213 -0
- package/lib/engine-reports.js +112 -33
- package/lib/git-snapshot-manager.js +99 -0
- package/lib/index.js +207 -396
- package/lib/milestone-manager.js +141 -0
- package/lib/routes.js +249 -138
- package/lib/token-budget-guard.js +128 -0
- package/lib/tools.js +115 -257
- package/package.json +4 -3
package/lib/index.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import z from '@deepseek-ai/schemastery';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { AddonStore } from './addon-store.js';
|
|
5
|
+
import { TokenBudgetGuard } from './token-budget-guard.js';
|
|
6
|
+
import { GitSnapshotManager } from './git-snapshot-manager.js';
|
|
7
|
+
import { MilestoneManager } from './milestone-manager.js';
|
|
6
8
|
import { registerRoutes } from './routes.js';
|
|
7
9
|
import { registerTools } from './tools.js';
|
|
10
|
+
import { saveGoalReportFile } from './engine-reports.js';
|
|
8
11
|
import { registerPluginUpdater } from './updater.js';
|
|
12
|
+
import { GoalEngine, GoalState } from './goal-engine.js';
|
|
13
|
+
import { sessionIdOf } from './goal-engine-constants.js';
|
|
9
14
|
|
|
10
15
|
export const name = '@goodandready/dsh-goal';
|
|
11
|
-
export const inject = ['webServer', 'settings'];
|
|
16
|
+
export const inject = ['webServer', 'settings', 'goals?'];
|
|
12
17
|
|
|
13
18
|
const NS = 'dsh-goal';
|
|
14
19
|
|
|
@@ -36,10 +41,6 @@ export const Config = z.object({
|
|
|
36
41
|
|
|
37
42
|
export function apply(ctx, config = {}) {
|
|
38
43
|
let settingsScope = null;
|
|
39
|
-
let agentsService = null;
|
|
40
|
-
const runningAgents = new Set();
|
|
41
|
-
const sessionAgents = new Map();
|
|
42
|
-
let lastActiveAgent = null;
|
|
43
44
|
const sseClients = new Map();
|
|
44
45
|
|
|
45
46
|
let currentSettings = {
|
|
@@ -72,433 +73,243 @@ export function apply(ctx, config = {}) {
|
|
|
72
73
|
const storagePath = customStorage || (isTest ? null : path.join(defaultStorageDir, 'dsh-goal-state.json'));
|
|
73
74
|
const logger = ctx.logger ? ctx.logger('goal') : console;
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
// Initialize addon modules
|
|
77
|
+
const addonStore = new AddonStore(storagePath, logger);
|
|
78
|
+
const gitSnapshotManager = new GitSnapshotManager(addonStore, { logger });
|
|
79
|
+
const milestoneManager = new MilestoneManager(addonStore, {
|
|
80
|
+
gitSnapshotManager,
|
|
81
|
+
enforceDependencies: currentSettings.enableMilestoneDependencies,
|
|
82
|
+
logger,
|
|
83
|
+
});
|
|
84
|
+
const tokenBudgetGuard = new TokenBudgetGuard({
|
|
81
85
|
maxTokenBudget: currentSettings.maxTokenBudget,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
enableLiveActivityFeed: currentSettings.enableLiveActivityFeed,
|
|
85
|
-
enablePreplanning: currentSettings.enablePreplanning,
|
|
86
|
-
autoBranchOnGoalStart: currentSettings.autoBranchOnGoalStart,
|
|
87
|
-
enablePostGoalRetrospective: currentSettings.enablePostGoalRetrospective,
|
|
88
|
-
autoScaleBudgetNearCompletion: currentSettings.autoScaleBudgetNearCompletion,
|
|
89
|
-
enableTemplatesDrawer: currentSettings.enableTemplatesDrawer,
|
|
90
|
-
enableMilestoneDependencies: currentSettings.enableMilestoneDependencies,
|
|
91
|
-
soundScheme: currentSettings.soundScheme,
|
|
92
|
-
enableVoiceAnnouncements: currentSettings.enableVoiceAnnouncements,
|
|
93
|
-
storagePath,
|
|
86
|
+
warningThreshold: currentSettings.budgetWarningThreshold,
|
|
87
|
+
autoScaleNearCompletion: currentSettings.autoScaleBudgetNearCompletion,
|
|
94
88
|
logger,
|
|
95
89
|
});
|
|
96
90
|
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
// Attempt migration of legacy state if present
|
|
92
|
+
try {
|
|
93
|
+
const firstAgent = ctx.agents?.get?.(ctx.agents?.keys?.()?.next()?.value);
|
|
94
|
+
addonStore.migrateLegacyState(storagePath, ctx.goals, firstAgent);
|
|
95
|
+
} catch (migErr) {
|
|
96
|
+
logger.warn?.('[dsh-goal] Legacy migration check failed:', migErr.message);
|
|
97
|
+
}
|
|
101
98
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (sid) {
|
|
108
|
-
sessionAgents.set(sid, agent);
|
|
109
|
-
}
|
|
110
|
-
} else {
|
|
111
|
-
runningAgents.delete(agent);
|
|
112
|
-
if (lastActiveAgent === agent && (status === 'stopped' || status === 'completed' || status === 'error')) {
|
|
113
|
-
lastActiveAgent = null;
|
|
114
|
-
}
|
|
115
|
-
const sid = sessionIdOf(agent, null);
|
|
116
|
-
if (sid && sessionAgents.get(sid) === agent && (status === 'stopped' || status === 'completed' || status === 'error')) {
|
|
117
|
-
sessionAgents.delete(sid);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
99
|
+
// Register addon tools: goal_milestones and goal_checklist
|
|
100
|
+
registerTools(ctx, {
|
|
101
|
+
milestoneManager,
|
|
102
|
+
goalsService: ctx.goals,
|
|
103
|
+
logger,
|
|
120
104
|
});
|
|
121
105
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
logger.warn('Failed to cancel running agent:', err);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
106
|
+
// Support fallback engine for tests or non-core environments
|
|
107
|
+
let fallbackEngine = null;
|
|
108
|
+
if (!ctx.goals) {
|
|
109
|
+
fallbackEngine = new GoalEngine({
|
|
110
|
+
storagePath,
|
|
111
|
+
defaultMaxIterations: currentSettings.maxIterations,
|
|
112
|
+
autoDrive: currentSettings.autoDrive,
|
|
113
|
+
consecutiveToolFailureLimit: currentSettings.consecutiveToolFailureLimit,
|
|
114
|
+
maxTokenBudget: currentSettings.maxTokenBudget,
|
|
115
|
+
budgetWarningThreshold: currentSettings.budgetWarningThreshold,
|
|
116
|
+
autoCheckpointOnMilestone: currentSettings.autoCheckpointOnMilestone,
|
|
117
|
+
enableLiveActivityFeed: currentSettings.enableLiveActivityFeed,
|
|
118
|
+
enablePreplanning: currentSettings.enablePreplanning,
|
|
119
|
+
autoBranchOnGoalStart: currentSettings.autoBranchOnGoalStart,
|
|
120
|
+
enablePostGoalRetrospective: currentSettings.enablePostGoalRetrospective,
|
|
121
|
+
autoScaleBudgetNearCompletion: currentSettings.autoScaleBudgetNearCompletion,
|
|
122
|
+
enableTemplatesDrawer: currentSettings.enableTemplatesDrawer,
|
|
123
|
+
enableMilestoneDependencies: currentSettings.enableMilestoneDependencies,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
145
126
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
127
|
+
// 1. One-click auto-updater registration (Issue #99)
|
|
128
|
+
if (ctx?.webServer?.register) {
|
|
129
|
+
if (typeof ctx?.effect === 'function') {
|
|
130
|
+
ctx.effect(() => {
|
|
131
|
+
let unreg = null;
|
|
149
132
|
try {
|
|
150
|
-
|
|
133
|
+
unreg = registerPluginUpdater(ctx, {
|
|
134
|
+
packageName: '@goodandready/dsh-goal',
|
|
135
|
+
endpoint: '/api/dsh-goal/update',
|
|
136
|
+
manifestUrl: new URL('../package.json', import.meta.url),
|
|
137
|
+
});
|
|
151
138
|
} catch (err) {
|
|
152
|
-
logger.warn('Failed to
|
|
139
|
+
logger.warn?.('Failed to register plugin updater:', err.message);
|
|
153
140
|
}
|
|
154
|
-
|
|
141
|
+
return () => {
|
|
142
|
+
if (typeof unreg === 'function') unreg();
|
|
143
|
+
};
|
|
144
|
+
}, 'dsh-goal: plugin auto-updater');
|
|
145
|
+
} else {
|
|
146
|
+
registerPluginUpdater(ctx, {
|
|
147
|
+
packageName: '@goodandready/dsh-goal',
|
|
148
|
+
endpoint: '/api/dsh-goal/update',
|
|
149
|
+
manifestUrl: new URL('../package.json', import.meta.url),
|
|
150
|
+
});
|
|
155
151
|
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const resumeActiveAgent = (promptText, sessionId = 'default') => {
|
|
159
|
-
const sid = sessionId || 'default';
|
|
160
|
-
const snap = engine.getSnapshot(sid);
|
|
161
|
-
const lang = snap.lang || (snap.title ? detectLanguage(snap.title) : 'en');
|
|
162
|
-
const defaultResumePrompt = lang === 'zh'
|
|
163
|
-
? '▶️ 用户已恢复目标。请继续执行工作计划,并通过 goal_update_progress 更新进度。'
|
|
164
|
-
: '▶️ Goal resumed by user. Continue executing the work plan from where you stopped. Update steps via goal_update_progress.';
|
|
152
|
+
}
|
|
165
153
|
|
|
166
|
-
|
|
154
|
+
// 2. WebServer HTTP & SSE routes (registered after updater so it remains primary)
|
|
155
|
+
const { broadcastEvent } = registerRoutes(ctx, {
|
|
156
|
+
engine: fallbackEngine,
|
|
157
|
+
milestoneManager,
|
|
158
|
+
gitSnapshotManager,
|
|
159
|
+
tokenBudgetGuard,
|
|
160
|
+
addonStore,
|
|
161
|
+
goalsService: ctx.goals,
|
|
162
|
+
getConfig: () => currentSettings,
|
|
163
|
+
sseClients,
|
|
164
|
+
logger,
|
|
165
|
+
});
|
|
167
166
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!
|
|
172
|
-
|
|
167
|
+
// 3. Listen to core goal/changed events if ctx.on exists
|
|
168
|
+
if (typeof ctx?.on === 'function') {
|
|
169
|
+
ctx.on('goal/changed', async ({ agent, change }) => {
|
|
170
|
+
if (!change) return;
|
|
171
|
+
const op = change.operation;
|
|
172
|
+
const goal = change.goal;
|
|
173
|
+
const goalId = goal?.id || change.ref?.id;
|
|
174
|
+
|
|
175
|
+
if (op === 'create' || op === 'resume') {
|
|
176
|
+
tokenBudgetGuard.resetGoal(goalId);
|
|
177
|
+
broadcastEvent('goal_status', { op, goalId, goal });
|
|
173
178
|
}
|
|
174
|
-
}
|
|
175
179
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
180
|
+
if (op === 'complete' || op === 'clear') {
|
|
181
|
+
try {
|
|
182
|
+
const milestones = milestoneManager.getMilestones(goalId);
|
|
183
|
+
const snapshots = addonStore.getSnapshots(goalId);
|
|
184
|
+
const savedPath = saveGoalReportFile(goal || { id: goalId, objective: 'Completed Goal' }, {
|
|
185
|
+
milestones,
|
|
186
|
+
snapshots,
|
|
187
|
+
});
|
|
188
|
+
if (savedPath) {
|
|
189
|
+
logger.info?.(`[dsh-goal] Saved goal report to "${savedPath}"`);
|
|
185
190
|
}
|
|
191
|
+
} catch (err) {
|
|
192
|
+
logger.warn?.('[dsh-goal] Failed to save goal report on completion:', err.message);
|
|
186
193
|
}
|
|
187
|
-
|
|
188
|
-
target = list[list.length - 1];
|
|
189
|
-
}
|
|
194
|
+
broadcastEvent('goal_status', { op, goalId, goal });
|
|
190
195
|
}
|
|
191
|
-
}
|
|
192
196
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
lastActiveAgent = target;
|
|
197
|
-
sessionAgents.set(sid, target);
|
|
198
|
-
return true;
|
|
199
|
-
} catch (err) {
|
|
200
|
-
logger.warn('Failed to resume agent:', err);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return false;
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
const getConfig = () => {
|
|
207
|
-
if (settingsScope) {
|
|
208
|
-
const snap = typeof settingsScope.getSnapshot === 'function' ? settingsScope.getSnapshot() : null;
|
|
209
|
-
let live = null;
|
|
210
|
-
if (snap) {
|
|
211
|
-
if (snap.status === 'ready' || snap.status === 'writable' || snap.status === undefined) {
|
|
212
|
-
live = snap.value || (typeof settingsScope.get === 'function' ? settingsScope.get() : null);
|
|
213
|
-
}
|
|
214
|
-
} else if (typeof settingsScope.get === 'function') {
|
|
215
|
-
live = settingsScope.get();
|
|
197
|
+
if (op === 'block') {
|
|
198
|
+
logger.info?.(`[dsh-goal] Goal "${goalId}" blocked: ${goal?.blockedReason?.message || 'Policy block'}`);
|
|
199
|
+
broadcastEvent('goal_status', { op, goalId, goal });
|
|
216
200
|
}
|
|
217
|
-
if (live && typeof live === 'object') {
|
|
218
|
-
return {
|
|
219
|
-
...currentSettings,
|
|
220
|
-
...live,
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return currentSettings;
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
const applySettings = () => {
|
|
228
|
-
const live = getConfig();
|
|
229
|
-
engine.updateConfig({
|
|
230
|
-
defaultMaxIterations: live.maxIterations,
|
|
231
|
-
autoDrive: live.autoDrive,
|
|
232
|
-
enableSound: live.enableSound,
|
|
233
|
-
showQuickLaunchButton: live.showQuickLaunchButton,
|
|
234
|
-
consecutiveToolFailureLimit: live.consecutiveToolFailureLimit,
|
|
235
|
-
maxTokenBudget: live.maxTokenBudget,
|
|
236
|
-
budgetWarningThreshold: live.budgetWarningThreshold,
|
|
237
|
-
autoCheckpointOnMilestone: live.autoCheckpointOnMilestone,
|
|
238
|
-
enableLiveActivityFeed: live.enableLiveActivityFeed,
|
|
239
|
-
enablePreplanning: live.enablePreplanning,
|
|
240
|
-
autoBranchOnGoalStart: live.autoBranchOnGoalStart,
|
|
241
|
-
enablePostGoalRetrospective: live.enablePostGoalRetrospective,
|
|
242
|
-
autoScaleBudgetNearCompletion: live.autoScaleBudgetNearCompletion,
|
|
243
|
-
enableTemplatesDrawer: live.enableTemplatesDrawer,
|
|
244
|
-
enableMilestoneDependencies: live.enableMilestoneDependencies,
|
|
245
|
-
soundScheme: live.soundScheme,
|
|
246
|
-
enableVoiceAnnouncements: live.enableVoiceAnnouncements,
|
|
247
201
|
});
|
|
248
|
-
};
|
|
249
202
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
|
|
203
|
+
// Inject milestone prompts and check budget during agent turns
|
|
204
|
+
ctx.on('agent/pre-step', async ({ agent, messages, step, signal }, next) => {
|
|
205
|
+
if (ctx.goals && typeof ctx.goals.get === 'function' && agent) {
|
|
206
|
+
try {
|
|
207
|
+
const currentGoal = ctx.goals.get(agent);
|
|
208
|
+
if (currentGoal && currentGoal.phase === 'active') {
|
|
209
|
+
const goalId = currentGoal.id;
|
|
210
|
+
|
|
211
|
+
// 1. Budget check
|
|
212
|
+
const milestones = milestoneManager.getMilestones(goalId);
|
|
213
|
+
const budgetCheck = tokenBudgetGuard.checkBudget({
|
|
214
|
+
agent,
|
|
215
|
+
goal: currentGoal,
|
|
216
|
+
tokenMeter: ctx.tokenMeter,
|
|
217
|
+
goalsService: ctx.goals,
|
|
218
|
+
milestones,
|
|
261
219
|
});
|
|
262
|
-
return () => {
|
|
263
|
-
if (typeof off === 'function') off();
|
|
264
|
-
settingsScope = null;
|
|
265
|
-
};
|
|
266
|
-
}, 'dsh-goal: settings subscription');
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
} catch (err) {
|
|
270
|
-
logger.warn('Settings register skipped:', err.message);
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
// 2. Chat slash command /goal registration
|
|
275
|
-
ctx.inject(['commands'], (cctx) => {
|
|
276
|
-
try {
|
|
277
|
-
if (typeof cctx.commands?.register !== 'function') return;
|
|
278
|
-
const unregister = cctx.commands.register({
|
|
279
|
-
name: 'goal',
|
|
280
|
-
description: 'Manage Goal Mode: activate, milestones, pause, resume, and clear',
|
|
281
|
-
input: { hint: '[<objective>|clear|pause|resume]' },
|
|
282
|
-
handler: async (invocation) => {
|
|
283
|
-
const sid = sessionIdOf(invocation, 'default');
|
|
284
|
-
if (invocation?.agent) {
|
|
285
|
-
lastActiveAgent = invocation.agent;
|
|
286
|
-
sessionAgents.set(sid, invocation.agent);
|
|
287
|
-
}
|
|
288
|
-
const parsed = parseGoalInput(invocation?.rawInput);
|
|
289
|
-
const liveConfig = getConfig();
|
|
290
|
-
return executeGoalSlashCommand(engine, parsed, liveConfig, invocation?.agent, sid);
|
|
291
|
-
},
|
|
292
|
-
});
|
|
293
220
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
let displacedPromptSection = null;
|
|
308
|
-
if (globalSections?.data instanceof Map && globalSections.data.has('tool:goal')) {
|
|
309
|
-
displacedPromptSection = globalSections.data.get('tool:goal');
|
|
310
|
-
globalSections.data.delete('tool:goal');
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const order = typeof pctx.systemPrompt.getSectionOrder === 'function'
|
|
314
|
-
? (pctx.systemPrompt.getSectionOrder('TOOL_GOAL') || 2400)
|
|
315
|
-
: 2400;
|
|
316
|
-
|
|
317
|
-
const unregisterSection = pctx.systemPrompt.section({
|
|
318
|
-
name: 'tool:dsh-goal',
|
|
319
|
-
order,
|
|
320
|
-
text: (sessionCtx) => {
|
|
321
|
-
const sid = sessionIdOf(sessionCtx, 'default');
|
|
322
|
-
return engine.getStatePromptInjection(sid);
|
|
323
|
-
},
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
if (typeof unregisterSection === 'function') {
|
|
327
|
-
pctx.effect(() => () => {
|
|
328
|
-
unregisterSection();
|
|
329
|
-
if (displacedPromptSection && globalSections?.data instanceof Map) {
|
|
330
|
-
globalSections.data.set('tool:goal', displacedPromptSection);
|
|
221
|
+
// 2. Active milestone prompt guidance
|
|
222
|
+
const milestonePrompt = milestoneManager.getActiveMilestonePrompt(goalId);
|
|
223
|
+
if (milestonePrompt && Array.isArray(messages)) {
|
|
224
|
+
let promptText = milestonePrompt;
|
|
225
|
+
if (budgetCheck.status === 'warning') {
|
|
226
|
+
promptText += `\n${budgetCheck.message}`;
|
|
227
|
+
} else if (budgetCheck.status === 'auto_scaled') {
|
|
228
|
+
promptText += `\n${budgetCheck.message}`;
|
|
229
|
+
}
|
|
230
|
+
messages.push({
|
|
231
|
+
role: 'system',
|
|
232
|
+
content: promptText,
|
|
233
|
+
});
|
|
331
234
|
}
|
|
332
|
-
}
|
|
235
|
+
}
|
|
236
|
+
} catch (err) {
|
|
237
|
+
logger.warn?.('[dsh-goal] agent/pre-step error:', err.message);
|
|
333
238
|
}
|
|
334
239
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
});
|
|
240
|
+
return next ? next() : undefined;
|
|
241
|
+
});
|
|
339
242
|
|
|
340
|
-
|
|
341
|
-
|
|
243
|
+
// Compatibility events for turn/end and approval/asked
|
|
244
|
+
ctx.on('turn/end', (event) => {
|
|
245
|
+
// noop or session isolation
|
|
246
|
+
});
|
|
342
247
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
packageName: '@goodandready/dsh-goal',
|
|
350
|
-
endpoint: '/api/dsh-goal/update',
|
|
351
|
-
manifestUrl: new URL('../package.json', import.meta.url),
|
|
352
|
-
});
|
|
353
|
-
} catch (err) {
|
|
354
|
-
logger.warn('Failed to register plugin updater:', err.message);
|
|
248
|
+
ctx.on('approval/asked', (event) => {
|
|
249
|
+
if (!fallbackEngine) return;
|
|
250
|
+
const sid = sessionIdOf(event, 'default');
|
|
251
|
+
const snap = fallbackEngine.getSnapshot(sid);
|
|
252
|
+
if (snap && snap.hasActiveGoal && snap.state === GoalState.RUNNING) {
|
|
253
|
+
fallbackEngine.pause('Waiting for operator confirmation/approval', sid);
|
|
355
254
|
}
|
|
356
|
-
|
|
357
|
-
if (typeof unreg === 'function') unreg();
|
|
358
|
-
};
|
|
359
|
-
}, 'dsh-goal: plugin auto-updater');
|
|
255
|
+
});
|
|
360
256
|
}
|
|
361
257
|
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (!liveConfig.autoDrive) return;
|
|
377
|
-
|
|
378
|
-
const sid = sessionIdOf(turn, 'default');
|
|
379
|
-
const usage = turn?.usage || turn?.meta?.usage || turn?.response?.usage || turn?.turn?.usage;
|
|
380
|
-
if (usage) {
|
|
381
|
-
engine.addTokenUsage(usage, sid);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const snap = engine.getSnapshot(sid);
|
|
385
|
-
if (snap.hasActiveGoal && snap.state === GoalState.RUNNING) {
|
|
386
|
-
const steps = turn?.steps || turn?.turn?.steps || [];
|
|
387
|
-
const hasStepError = Array.isArray(steps) && steps.some((s) => {
|
|
388
|
-
return s?.status === 'error' || s?.error || s?.toolResult?.isError;
|
|
389
|
-
});
|
|
390
|
-
const hasTurnError = Boolean(turn?.error || turn?.reason?.kind === 'error');
|
|
391
|
-
const isToolFailure = hasStepError || hasTurnError;
|
|
258
|
+
// Settings synchronization
|
|
259
|
+
const applySettings = (patch) => {
|
|
260
|
+
if (!patch || typeof patch !== 'object') return;
|
|
261
|
+
currentSettings = { ...currentSettings, ...patch };
|
|
262
|
+
tokenBudgetGuard.updateConfig({
|
|
263
|
+
maxTokenBudget: currentSettings.maxTokenBudget,
|
|
264
|
+
warningThreshold: currentSettings.budgetWarningThreshold,
|
|
265
|
+
autoScaleNearCompletion: currentSettings.autoScaleBudgetNearCompletion,
|
|
266
|
+
});
|
|
267
|
+
milestoneManager.enforceDependencies = Boolean(currentSettings.enableMilestoneDependencies);
|
|
268
|
+
if (typeof patch.storagePath === 'string' && patch.storagePath.trim()) {
|
|
269
|
+
addonStore.setStoragePath(patch.storagePath.trim());
|
|
270
|
+
}
|
|
271
|
+
};
|
|
392
272
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
engine.pause(pauseReason, sid);
|
|
402
|
-
return;
|
|
273
|
+
if (typeof ctx?.inject === 'function') {
|
|
274
|
+
ctx.inject(['settings'], (sctx) => {
|
|
275
|
+
try {
|
|
276
|
+
const scope = sctx.settings?.register?.(NS, Config, { base: currentSettings });
|
|
277
|
+
if (scope) {
|
|
278
|
+
settingsScope = scope;
|
|
279
|
+
if (typeof scope.get === 'function') {
|
|
280
|
+
applySettings(scope.get());
|
|
403
281
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (stallCount >= 2) {
|
|
415
|
-
const stallReason = lang === 'zh'
|
|
416
|
-
? '智能体在过去 2 次迭代中未推进工作计划 — 需要操作员关注'
|
|
417
|
-
: 'Agent made no progress on work plan in the last 2 iterations — operator attention required';
|
|
418
|
-
engine.pause(stallReason, sid);
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const canContinue = engine.incrementIteration(sid);
|
|
423
|
-
if (!canContinue) return;
|
|
424
|
-
|
|
425
|
-
const triggerNextTurn = () => {
|
|
426
|
-
const currentSnap = engine.getSnapshot(sid);
|
|
427
|
-
if (currentSnap.hasActiveGoal && currentSnap.state === GoalState.RUNNING) {
|
|
428
|
-
const currentLang = currentSnap.lang || (currentSnap.title ? detectLanguage(currentSnap.title) : 'en');
|
|
429
|
-
let promptText = currentLang === 'zh'
|
|
430
|
-
? '请按照工作计划继续自主执行目标。每完成一步请通过 goal_update_progress 记录,所有任务完成后调用 goal_finish。'
|
|
431
|
-
: '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.';
|
|
432
|
-
|
|
433
|
-
if (currentSnap.pendingNudge) {
|
|
434
|
-
const userNudge = engine.consumePendingNudge(sid);
|
|
435
|
-
if (userNudge) {
|
|
436
|
-
promptText = (currentLang === 'zh'
|
|
437
|
-
? `🚨 用户紧急说明/指导方向:\n"${userNudge}"\n请根据此反馈调整执行。\n\n`
|
|
438
|
-
: `🚨 URGENT USER CLARIFICATION / STEERING:\n"${userNudge}"\nAdjust execution adhering to this feedback.\n\n`) + promptText;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
const promptMsg = createGoalUserMessage(promptText);
|
|
443
|
-
let target = sessionAgents.get(sid);
|
|
444
|
-
if (!target && lastActiveAgent && typeof lastActiveAgent.followup === 'function') {
|
|
445
|
-
const lastSid = sessionIdOf(lastActiveAgent, null);
|
|
446
|
-
if (!lastSid || lastSid === sid || sid === 'default') {
|
|
447
|
-
target = lastActiveAgent;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if (!target && agentsService && typeof agentsService.list === 'function') {
|
|
452
|
-
const list = agentsService.list();
|
|
453
|
-
if (list && list.length > 0) {
|
|
454
|
-
for (let i = list.length - 1; i >= 0; i--) {
|
|
455
|
-
const cand = list[i];
|
|
456
|
-
if (sessionIdOf(cand, null) === sid) {
|
|
457
|
-
target = cand;
|
|
458
|
-
break;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
if (!target && sid === 'default') target = list[list.length - 1];
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
if (target && typeof target.followup === 'function' && target.status !== 'stopped') {
|
|
466
|
-
try {
|
|
467
|
-
target.followup(promptMsg);
|
|
468
|
-
sessionAgents.set(sid, target);
|
|
469
|
-
} catch (err) {
|
|
470
|
-
logger.warn('Failed auto-drive followup:', err);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
282
|
+
if (typeof scope.subscribe === 'function') {
|
|
283
|
+
sctx.effect?.(() => {
|
|
284
|
+
const off = scope.subscribe(() => {
|
|
285
|
+
if (typeof scope.get === 'function') applySettings(scope.get());
|
|
286
|
+
});
|
|
287
|
+
return () => {
|
|
288
|
+
if (typeof off === 'function') off();
|
|
289
|
+
settingsScope = null;
|
|
290
|
+
};
|
|
291
|
+
}, 'dsh-goal: settings subscription');
|
|
473
292
|
}
|
|
474
|
-
};
|
|
475
|
-
|
|
476
|
-
if (typeof setImmediate === 'function') {
|
|
477
|
-
setImmediate(triggerNextTurn);
|
|
478
|
-
} else {
|
|
479
|
-
setTimeout(triggerNextTurn, 0);
|
|
480
293
|
}
|
|
294
|
+
} catch (err) {
|
|
295
|
+
logger.warn?.('Settings register skipped:', err.message);
|
|
481
296
|
}
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
const onApprovalAsked = (event) => {
|
|
485
|
-
const sid = sessionIdOf(event, 'default');
|
|
486
|
-
const snap = engine.getSnapshot(sid);
|
|
487
|
-
if (snap.hasActiveGoal && snap.state === GoalState.RUNNING) {
|
|
488
|
-
const lang = snap.lang || (snap.title ? detectLanguage(snap.title) : 'en');
|
|
489
|
-
const pauseReason = lang === 'zh'
|
|
490
|
-
? '等待操作员确认/批准操作'
|
|
491
|
-
: 'Waiting for operator confirmation/approval';
|
|
492
|
-
engine.pause(pauseReason, sid);
|
|
493
|
-
}
|
|
494
|
-
};
|
|
297
|
+
});
|
|
298
|
+
}
|
|
495
299
|
|
|
496
|
-
|
|
497
|
-
ctx.
|
|
300
|
+
if (typeof ctx?.effect === 'function') {
|
|
301
|
+
ctx.effect(() => () => {
|
|
302
|
+
try {
|
|
303
|
+
addonStore.flushSync();
|
|
304
|
+
} catch (e) { /* ignore */ }
|
|
305
|
+
}, 'dsh-goal: teardown');
|
|
306
|
+
}
|
|
498
307
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
308
|
+
return {
|
|
309
|
+
addonStore,
|
|
310
|
+
milestoneManager,
|
|
311
|
+
tokenBudgetGuard,
|
|
312
|
+
gitSnapshotManager,
|
|
313
|
+
engine: fallbackEngine,
|
|
314
|
+
};
|
|
504
315
|
}
|