neoagent 3.2.1-beta.2 → 3.2.1-beta.4
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/extensions/chrome-browser/background.mjs +318 -88
- package/extensions/chrome-browser/http.mjs +136 -0
- package/extensions/chrome-browser/protocol.mjs +511 -89
- package/flutter_app/lib/main_chat.dart +118 -739
- package/flutter_app/lib/main_controller.dart +29 -20
- package/flutter_app/lib/main_models.dart +3 -0
- package/flutter_app/lib/main_operations.dart +334 -321
- package/flutter_app/lib/main_settings.dart +4 -3
- package/flutter_app/lib/main_shared.dart +14 -11
- package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
- package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
- package/flutter_app/windows/runner/flutter_window.cpp +143 -32
- package/lib/manager.js +106 -89
- package/lib/schema_migrations.js +67 -13
- package/package.json +20 -13
- package/runtime/paths.js +49 -5
- package/server/guest_agent.js +52 -30
- package/server/http/middleware.js +24 -0
- package/server/http/routes.js +4 -6
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +30803 -30805
- package/server/routes/admin.js +1 -1
- package/server/routes/android.js +30 -34
- package/server/routes/browser.js +23 -15
- package/server/routes/desktop.js +18 -1
- package/server/routes/integrations.js +5 -1
- package/server/routes/memory.js +1 -0
- package/server/routes/settings.js +16 -5
- package/server/routes/social_reach.js +12 -3
- package/server/routes/social_video.js +4 -0
- package/server/services/ai/compaction.js +7 -2
- package/server/services/ai/history.js +44 -5
- package/server/services/ai/integrated_tools/http_request.js +8 -0
- package/server/services/ai/loop/agent_engine_core.js +347 -162
- package/server/services/ai/loop/callbacks.js +1 -0
- package/server/services/ai/loop/completion_judge.js +12 -0
- package/server/services/ai/loop/conversation_loop.js +348 -242
- package/server/services/ai/loop/messaging_delivery.js +129 -57
- package/server/services/ai/loop/model_call_guard.js +91 -0
- package/server/services/ai/loop/model_io.js +8 -41
- package/server/services/ai/loop/tool_dispatch.js +19 -8
- package/server/services/ai/loopPolicy.js +24 -19
- package/server/services/ai/model_discovery.js +227 -0
- package/server/services/ai/model_identity.js +71 -0
- package/server/services/ai/models.js +67 -162
- package/server/services/ai/providerRetry.js +17 -59
- package/server/services/ai/provider_selector.js +111 -0
- package/server/services/ai/providers/anthropic.js +2 -2
- package/server/services/ai/providers/claudeCode.js +15 -28
- package/server/services/ai/providers/githubCopilot.js +36 -16
- package/server/services/ai/providers/google.js +23 -5
- package/server/services/ai/providers/grok.js +4 -3
- package/server/services/ai/providers/grokOauth.js +13 -22
- package/server/services/ai/providers/nvidia.js +10 -5
- package/server/services/ai/providers/ollama.js +102 -82
- package/server/services/ai/providers/ollama_stream.js +142 -0
- package/server/services/ai/providers/openai.js +39 -5
- package/server/services/ai/providers/openaiCodex.js +11 -4
- package/server/services/ai/providers/openrouter.js +29 -7
- package/server/services/ai/providers/provider_error.js +36 -0
- package/server/services/ai/settings.js +26 -2
- package/server/services/ai/taskAnalysis.js +5 -1
- package/server/services/ai/terminal_reply.js +45 -0
- package/server/services/ai/toolEvidence.js +58 -29
- package/server/services/ai/tools.js +124 -111
- package/server/services/android/controller.js +770 -237
- package/server/services/android/process.js +140 -0
- package/server/services/android/sdk_download.js +143 -0
- package/server/services/android/uia.js +6 -5
- package/server/services/artifacts/store.js +24 -0
- package/server/services/browser/controller.js +736 -385
- package/server/services/browser/extension/gateway.js +40 -16
- package/server/services/browser/extension/protocol.js +12 -1
- package/server/services/browser/extension/provider.js +59 -47
- package/server/services/browser/extension/registry.js +155 -34
- package/server/services/cli/executor.js +62 -9
- package/server/services/desktop/gateway.js +41 -4
- package/server/services/desktop/protocol.js +3 -0
- package/server/services/desktop/provider.js +39 -42
- package/server/services/desktop/registry.js +137 -52
- package/server/services/integrations/figma/provider.js +78 -12
- package/server/services/integrations/github/common.js +11 -6
- package/server/services/integrations/github/provider.js +52 -53
- package/server/services/integrations/google/provider.js +55 -19
- package/server/services/integrations/home_assistant/network.js +17 -20
- package/server/services/integrations/home_assistant/provider.js +7 -5
- package/server/services/integrations/home_assistant/tools.js +17 -5
- package/server/services/integrations/http.js +51 -0
- package/server/services/integrations/manager.js +158 -53
- package/server/services/integrations/microsoft/provider.js +80 -13
- package/server/services/integrations/neoarchive/provider.js +55 -29
- package/server/services/integrations/neorecall/client.js +17 -10
- package/server/services/integrations/neorecall/provider.js +20 -11
- package/server/services/integrations/notion/provider.js +16 -13
- package/server/services/integrations/oauth_provider.js +115 -51
- package/server/services/integrations/slack/provider.js +98 -9
- package/server/services/integrations/spotify/provider.js +67 -71
- package/server/services/integrations/trello/provider.js +21 -7
- package/server/services/integrations/weather/provider.js +18 -12
- package/server/services/integrations/whatsapp/provider.js +76 -16
- package/server/services/manager.js +87 -1
- package/server/services/memory/embedding_index.js +20 -8
- package/server/services/memory/embeddings.js +151 -90
- package/server/services/memory/ingestion.js +50 -9
- package/server/services/memory/ingestion_documents.js +13 -3
- package/server/services/memory/manager.js +52 -19
- package/server/services/messaging/automation.js +84 -9
- package/server/services/messaging/http_platforms.js +33 -13
- package/server/services/messaging/inbound_queue.js +78 -24
- package/server/services/messaging/inbound_store.js +224 -0
- package/server/services/messaging/manager.js +326 -51
- package/server/services/messaging/typing_keepalive.js +5 -2
- package/server/services/messaging/whatsapp.js +22 -14
- package/server/services/network/http.js +210 -0
- package/server/services/network/safe_request.js +307 -0
- package/server/services/runtime/backends/local-vm.js +214 -66
- package/server/services/runtime/manager.js +17 -12
- package/server/services/social_reach/channels/github.js +10 -4
- package/server/services/social_reach/channels/reddit.js +4 -4
- package/server/services/social_reach/channels/rss.js +2 -2
- package/server/services/social_reach/channels/social_video.js +12 -7
- package/server/services/social_reach/channels/v2ex.js +21 -8
- package/server/services/social_reach/channels/x.js +2 -2
- package/server/services/social_reach/channels/xueqiu.js +5 -5
- package/server/services/social_reach/service.js +9 -6
- package/server/services/social_reach/utils.js +65 -14
- package/server/services/social_video/service.js +160 -50
- package/server/services/tasks/integration_runtime.js +18 -8
- package/server/services/tasks/runtime.js +39 -4
- package/server/services/voice/agentBridge.js +17 -4
- package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
- package/server/services/voice/liveSession.js +31 -0
- package/server/services/voice/openaiSpeech.js +33 -8
- package/server/services/voice/providers.js +233 -151
- package/server/services/voice/runtimeManager.js +118 -20
- package/server/services/voice/turnRunner.js +6 -0
- package/server/services/wearable/firmware_manifest.js +51 -13
- package/server/services/wearable/service.js +1 -0
- package/server/utils/abort.js +96 -0
- package/server/utils/cloud-security.js +110 -3
- package/server/utils/files.js +31 -0
- package/server/utils/image_payload.js +95 -0
- package/server/utils/retry.js +107 -0
|
@@ -19,8 +19,10 @@ const {
|
|
|
19
19
|
const { summarizeCapabilityHealth } = require('../capabilityHealth');
|
|
20
20
|
const { shouldAcceptTaskComplete } = require('../completion');
|
|
21
21
|
const { shortenRunId, summarizeForLog } = require('../logFormat');
|
|
22
|
+
const { getProviderForUser } = require('../provider_selector');
|
|
22
23
|
const { runConversation } = require('./conversation_loop');
|
|
23
24
|
const {
|
|
25
|
+
TERMINAL_STATUSES,
|
|
24
26
|
checkpointRun,
|
|
25
27
|
closeRun,
|
|
26
28
|
getRunControl,
|
|
@@ -30,6 +32,7 @@ const {
|
|
|
30
32
|
const {
|
|
31
33
|
buildChurnAssessmentPrompt,
|
|
32
34
|
buildCompletionDecisionPrompt,
|
|
35
|
+
enforceTerminalReplyDecision,
|
|
33
36
|
normalizeChurnAssessment,
|
|
34
37
|
normalizeCompletionDecision,
|
|
35
38
|
resolveRunGoalContext,
|
|
@@ -67,7 +70,7 @@ const {
|
|
|
67
70
|
const {
|
|
68
71
|
requestModelResponse: requestModelResponseImpl,
|
|
69
72
|
requestStructuredJson: requestStructuredJsonImpl,
|
|
70
|
-
|
|
73
|
+
runAbortableModelCall,
|
|
71
74
|
} = require('./model_io');
|
|
72
75
|
const {
|
|
73
76
|
publishInterimUpdate: publishInterimUpdateImpl,
|
|
@@ -82,6 +85,7 @@ const {
|
|
|
82
85
|
normalizeOutgoingMessage,
|
|
83
86
|
clampRunContext,
|
|
84
87
|
} = require('../messagingFallback');
|
|
88
|
+
const { isDeferredWorkReply } = require('../terminal_reply');
|
|
85
89
|
const {
|
|
86
90
|
summarizeToolExecutions,
|
|
87
91
|
} = require('../toolEvidence');
|
|
@@ -97,6 +101,13 @@ const {
|
|
|
97
101
|
normalizeRetrievalPlan,
|
|
98
102
|
shouldEnhanceRetrieval,
|
|
99
103
|
} = require('../../memory/retrieval_reasoning');
|
|
104
|
+
const {
|
|
105
|
+
createAbortError,
|
|
106
|
+
createLinkedAbortController,
|
|
107
|
+
isAbortError,
|
|
108
|
+
runWithAbortTimeout,
|
|
109
|
+
throwIfAborted,
|
|
110
|
+
} = require('../../../utils/abort');
|
|
100
111
|
|
|
101
112
|
function buildInitialRunMetadata(options = {}) {
|
|
102
113
|
const metadata = {};
|
|
@@ -106,6 +117,9 @@ function buildInitialRunMetadata(options = {}) {
|
|
|
106
117
|
if (options.widgetId != null && String(options.widgetId).trim()) {
|
|
107
118
|
metadata.widgetId = options.widgetId;
|
|
108
119
|
}
|
|
120
|
+
if (options.messagingInboundJobId != null && String(options.messagingInboundJobId).trim()) {
|
|
121
|
+
metadata.messagingInboundJobId = String(options.messagingInboundJobId).trim();
|
|
122
|
+
}
|
|
109
123
|
return metadata;
|
|
110
124
|
}
|
|
111
125
|
|
|
@@ -131,99 +145,6 @@ function buildAnalyzeTaskFallback(forceMode, userMessage = '') {
|
|
|
131
145
|
};
|
|
132
146
|
}
|
|
133
147
|
|
|
134
|
-
async function getProviderForUser(userId, task = '', isSubagent = false, modelOverride = null, providerConfig = {}) {
|
|
135
|
-
const { getSupportedModels, createProviderInstance } = require('../models');
|
|
136
|
-
const agentId = providerConfig.agentId || null;
|
|
137
|
-
const aiSettings = getAiSettings(userId, agentId);
|
|
138
|
-
const models = await getSupportedModels(userId, agentId);
|
|
139
|
-
|
|
140
|
-
let enabledIds = Array.isArray(aiSettings.enabled_models) ? aiSettings.enabled_models : [];
|
|
141
|
-
const defaultChatModel = aiSettings.default_chat_model || 'auto';
|
|
142
|
-
const defaultSubagentModel = aiSettings.default_subagent_model || 'auto';
|
|
143
|
-
const smarterSelection = aiSettings.smarter_model_selector !== false && aiSettings.smarter_model_selector !== 'false';
|
|
144
|
-
|
|
145
|
-
const knownModelIds = new Set(models.map((m) => m.id));
|
|
146
|
-
const selectableModels = models.filter((m) => m.available !== false);
|
|
147
|
-
|
|
148
|
-
enabledIds = Array.isArray(enabledIds)
|
|
149
|
-
? enabledIds
|
|
150
|
-
.map((id) => String(id))
|
|
151
|
-
.filter((id) => knownModelIds.has(id))
|
|
152
|
-
: [];
|
|
153
|
-
|
|
154
|
-
let availableModels = selectableModels.filter((m) => enabledIds.includes(m.id));
|
|
155
|
-
if (availableModels.length === 0) {
|
|
156
|
-
enabledIds = selectableModels.map((m) => m.id);
|
|
157
|
-
availableModels = [...selectableModels];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const fallbackModel = availableModels.length > 0 ? availableModels[0] : selectableModels[0];
|
|
161
|
-
|
|
162
|
-
if (!fallbackModel) {
|
|
163
|
-
throw new Error('No AI providers are currently available. Open Settings and configure at least one provider.');
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
let selectedModelDef = fallbackModel;
|
|
167
|
-
const userSelectedDefault = isSubagent ? defaultSubagentModel : defaultChatModel;
|
|
168
|
-
|
|
169
|
-
if (modelOverride && typeof modelOverride === 'string') {
|
|
170
|
-
const requested = models.find((m) => m.id === modelOverride.trim());
|
|
171
|
-
if (requested && requested.available !== false && enabledIds.includes(requested.id)) {
|
|
172
|
-
selectedModelDef = requested;
|
|
173
|
-
return {
|
|
174
|
-
provider: createProviderInstance(selectedModelDef.provider, userId, providerConfig),
|
|
175
|
-
model: selectedModelDef.id,
|
|
176
|
-
providerName: selectedModelDef.provider
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (userSelectedDefault && userSelectedDefault !== 'auto') {
|
|
182
|
-
selectedModelDef = models.find((m) => m.id === userSelectedDefault) || fallbackModel;
|
|
183
|
-
} else {
|
|
184
|
-
const selectionHint = providerConfig.selectionHint && typeof providerConfig.selectionHint === 'object'
|
|
185
|
-
? providerConfig.selectionHint
|
|
186
|
-
: {};
|
|
187
|
-
const preferredPurpose = String(selectionHint.purpose || '').trim().toLowerCase();
|
|
188
|
-
const highAutonomy = selectionHint.autonomyLevel === 'high' || selectionHint.complexity === 'complex';
|
|
189
|
-
const requiredConfidence = String(selectionHint.requiredConfidence || '').trim().toLowerCase();
|
|
190
|
-
const costMode = String(selectionHint.costMode || aiSettings.cost_mode || 'balanced_auto').trim().toLowerCase();
|
|
191
|
-
const requestedPurpose = ['planning', 'coding', 'general', 'fast'].includes(preferredPurpose)
|
|
192
|
-
? preferredPurpose
|
|
193
|
-
: '';
|
|
194
|
-
const priceRank = { free: 0, cheap: 1, medium: 2, expensive: 3 };
|
|
195
|
-
const chooseForPurpose = (purpose) => {
|
|
196
|
-
const candidates = availableModels.filter((model) => model.purpose === purpose);
|
|
197
|
-
if (candidates.length === 0) return null;
|
|
198
|
-
if (['economy', 'cost_saver', 'lowest_cost'].includes(costMode)) {
|
|
199
|
-
return [...candidates].sort((left, right) => (
|
|
200
|
-
(priceRank[left.priceTier] ?? 99) - (priceRank[right.priceTier] ?? 99)
|
|
201
|
-
))[0];
|
|
202
|
-
}
|
|
203
|
-
if (['quality', 'highest_quality'].includes(costMode) || requiredConfidence === 'high') {
|
|
204
|
-
return candidates.find((model) => model.priceTier !== 'free' && model.priceTier !== 'cheap') || candidates[0];
|
|
205
|
-
}
|
|
206
|
-
return candidates[0];
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
if (smarterSelection && requestedPurpose) {
|
|
210
|
-
selectedModelDef = chooseForPurpose(requestedPurpose) || fallbackModel;
|
|
211
|
-
} else if (smarterSelection && highAutonomy) {
|
|
212
|
-
selectedModelDef = chooseForPurpose('planning') || chooseForPurpose('general') || fallbackModel;
|
|
213
|
-
} else if (isSubagent) {
|
|
214
|
-
selectedModelDef = chooseForPurpose('fast') || fallbackModel;
|
|
215
|
-
} else {
|
|
216
|
-
selectedModelDef = chooseForPurpose('general') || fallbackModel;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return {
|
|
221
|
-
provider: createProviderInstance(selectedModelDef.provider, userId, providerConfig),
|
|
222
|
-
model: selectedModelDef.id,
|
|
223
|
-
providerName: selectedModelDef.provider
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
148
|
function estimateTokenValue(value) {
|
|
228
149
|
if (!value) return 0;
|
|
229
150
|
if (typeof value === 'string') return Math.ceil(value.length / 4);
|
|
@@ -234,6 +155,13 @@ class AgentEngine {
|
|
|
234
155
|
constructor(io, services = {}) {
|
|
235
156
|
this.io = io;
|
|
236
157
|
this.activeRuns = new Map();
|
|
158
|
+
this.activeRunPromises = new Set();
|
|
159
|
+
this.backgroundTasks = new Set();
|
|
160
|
+
this.backgroundTaskQueues = new Map();
|
|
161
|
+
this.subagentStartupTasks = new Set();
|
|
162
|
+
this.lifecycleAbortController = new AbortController();
|
|
163
|
+
this.shuttingDown = false;
|
|
164
|
+
this.shutdownPromise = null;
|
|
237
165
|
this.subagents = new Map();
|
|
238
166
|
this.app = services.app || null;
|
|
239
167
|
this.browserController = services.browserController || null;
|
|
@@ -249,6 +177,107 @@ class AgentEngine {
|
|
|
249
177
|
this.messagingDeliveryRetry = services.messagingDeliveryRetry || {};
|
|
250
178
|
}
|
|
251
179
|
|
|
180
|
+
trackBackgroundTask(task, options = {}) {
|
|
181
|
+
if (typeof task !== 'function') {
|
|
182
|
+
throw new TypeError('Background task must be a function.');
|
|
183
|
+
}
|
|
184
|
+
if (this.shuttingDown || this.lifecycleAbortController.signal.aborted) {
|
|
185
|
+
return Promise.reject(createAbortError(
|
|
186
|
+
this.lifecycleAbortController.signal,
|
|
187
|
+
'Agent engine is shutting down.',
|
|
188
|
+
));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const key = String(options.key || '').trim();
|
|
192
|
+
const previous = key ? this.backgroundTaskQueues.get(key) : null;
|
|
193
|
+
if (previous && options.coalesce === true) return previous;
|
|
194
|
+
|
|
195
|
+
const linked = createLinkedAbortController([
|
|
196
|
+
this.lifecycleAbortController.signal,
|
|
197
|
+
options.signal,
|
|
198
|
+
]);
|
|
199
|
+
const tracked = Promise.resolve().then(async () => {
|
|
200
|
+
if (previous) {
|
|
201
|
+
try {
|
|
202
|
+
await previous;
|
|
203
|
+
} catch {
|
|
204
|
+
// A failed predecessor must not permanently poison this queue.
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
throwIfAborted(linked.signal, 'Background task aborted.');
|
|
208
|
+
return task(linked.signal);
|
|
209
|
+
}).finally(() => {
|
|
210
|
+
linked.cleanup();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
this.backgroundTasks.add(tracked);
|
|
214
|
+
if (key) this.backgroundTaskQueues.set(key, tracked);
|
|
215
|
+
const cleanup = () => {
|
|
216
|
+
this.backgroundTasks.delete(tracked);
|
|
217
|
+
if (key && this.backgroundTaskQueues.get(key) === tracked) {
|
|
218
|
+
this.backgroundTaskQueues.delete(key);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
tracked.then(cleanup, cleanup);
|
|
222
|
+
return tracked;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
shutdown(options = {}) {
|
|
226
|
+
if (this.shutdownPromise) return this.shutdownPromise;
|
|
227
|
+
|
|
228
|
+
const reason = String(
|
|
229
|
+
options.reason || 'Server shutting down while agent work was in progress.',
|
|
230
|
+
);
|
|
231
|
+
const timeoutMs = Math.max(100, Number(options.timeoutMs) || 10000);
|
|
232
|
+
this.shuttingDown = true;
|
|
233
|
+
this.lifecycleAbortController.abort(reason);
|
|
234
|
+
this.interruptAllActiveRuns(reason);
|
|
235
|
+
|
|
236
|
+
const childShutdowns = [];
|
|
237
|
+
const childPromises = [];
|
|
238
|
+
for (const record of this.subagents.values()) {
|
|
239
|
+
if (typeof record.engine?.shutdown === 'function') {
|
|
240
|
+
childShutdowns.push(record.engine.shutdown({ reason, timeoutMs }));
|
|
241
|
+
}
|
|
242
|
+
if (record.promise) childPromises.push(record.promise);
|
|
243
|
+
}
|
|
244
|
+
const pending = [
|
|
245
|
+
...this.activeRunPromises,
|
|
246
|
+
...this.backgroundTasks,
|
|
247
|
+
...this.subagentStartupTasks,
|
|
248
|
+
...childShutdowns,
|
|
249
|
+
...childPromises,
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
this.shutdownPromise = (async () => {
|
|
253
|
+
if (pending.length === 0) {
|
|
254
|
+
return { state: 'stopped', timedOut: false, pendingCount: 0 };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let timeout = null;
|
|
258
|
+
const timeoutResult = Symbol('agent-engine-shutdown-timeout');
|
|
259
|
+
const result = await Promise.race([
|
|
260
|
+
Promise.allSettled(pending),
|
|
261
|
+
new Promise((resolve) => {
|
|
262
|
+
timeout = setTimeout(() => resolve(timeoutResult), timeoutMs);
|
|
263
|
+
}),
|
|
264
|
+
]);
|
|
265
|
+
if (timeout) clearTimeout(timeout);
|
|
266
|
+
const timedOut = result === timeoutResult;
|
|
267
|
+
return {
|
|
268
|
+
state: timedOut ? 'timeout' : 'stopped',
|
|
269
|
+
timedOut,
|
|
270
|
+
pendingCount: timedOut
|
|
271
|
+
? this.activeRunPromises.size
|
|
272
|
+
+ this.backgroundTasks.size
|
|
273
|
+
+ this.subagentStartupTasks.size
|
|
274
|
+
+ Array.from(this.subagents.values()).filter((record) => !record.settled).length
|
|
275
|
+
: 0,
|
|
276
|
+
};
|
|
277
|
+
})();
|
|
278
|
+
return this.shutdownPromise;
|
|
279
|
+
}
|
|
280
|
+
|
|
252
281
|
async buildSystemPrompt(userId, context = {}) {
|
|
253
282
|
const { buildSystemPromptSections } = require('../systemPrompt');
|
|
254
283
|
const { MemoryManager } = require('../../memory/manager');
|
|
@@ -287,18 +316,34 @@ class AgentEngine {
|
|
|
287
316
|
options = {},
|
|
288
317
|
returnDetails = false,
|
|
289
318
|
}) {
|
|
290
|
-
const
|
|
319
|
+
const signal = options.signal || this.getRunMeta(runId)?.abortController?.signal || null;
|
|
320
|
+
const initial = await memoryManager.recallMemory(userId, query, 12, {
|
|
321
|
+
agentId,
|
|
322
|
+
signal,
|
|
323
|
+
});
|
|
291
324
|
|
|
292
325
|
const pendingChunks = memoryManager.getPendingExtractionChunks?.(userId, agentId, 5) || [];
|
|
293
326
|
if (pendingChunks.length) {
|
|
294
|
-
this.extractPendingChunks(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
327
|
+
this.trackBackgroundTask((backgroundSignal) => this.extractPendingChunks(
|
|
328
|
+
pendingChunks,
|
|
329
|
+
{
|
|
330
|
+
userId,
|
|
331
|
+
agentId,
|
|
332
|
+
provider,
|
|
333
|
+
providerName,
|
|
334
|
+
model,
|
|
335
|
+
memoryManager,
|
|
336
|
+
signal: backgroundSignal,
|
|
337
|
+
},
|
|
338
|
+
), {
|
|
339
|
+
key: `memory-extraction:${userId}:${agentId || 'main'}`,
|
|
340
|
+
coalesce: true,
|
|
341
|
+
signal,
|
|
342
|
+
}).catch((err) => {
|
|
343
|
+
if (!isAbortError(err, signal) && !this.shuttingDown) {
|
|
344
|
+
console.warn('[Memory] Background chunk extraction failed:', err.message);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
302
347
|
}
|
|
303
348
|
|
|
304
349
|
const decision = shouldEnhanceRetrieval(initial);
|
|
@@ -335,7 +380,7 @@ class AgentEngine {
|
|
|
335
380
|
normalize: (raw) => normalizeRetrievalPlan(raw, query),
|
|
336
381
|
fallback: normalizeRetrievalPlan({}, query),
|
|
337
382
|
reasoningEffort: this.getReasoningEffort(providerName, options),
|
|
338
|
-
telemetry: { runId, stepId, userId, agentId },
|
|
383
|
+
telemetry: { runId, stepId, userId, agentId, signal },
|
|
339
384
|
phase: 'memory_retrieval_plan',
|
|
340
385
|
});
|
|
341
386
|
plan = planned.value;
|
|
@@ -346,6 +391,7 @@ class AgentEngine {
|
|
|
346
391
|
agentId,
|
|
347
392
|
validAt: plan.validAt,
|
|
348
393
|
includeHistory: plan.temporalMode === 'historical',
|
|
394
|
+
signal,
|
|
349
395
|
}));
|
|
350
396
|
}
|
|
351
397
|
merged = mergeRetrievalResults(resultSets, 30);
|
|
@@ -360,7 +406,7 @@ class AgentEngine {
|
|
|
360
406
|
normalize: (raw) => normalizeRerankResult(raw, merged),
|
|
361
407
|
fallback: merged,
|
|
362
408
|
reasoningEffort: this.getReasoningEffort(providerName, options),
|
|
363
|
-
telemetry: { runId, stepId, userId, agentId },
|
|
409
|
+
telemetry: { runId, stepId, userId, agentId, signal },
|
|
364
410
|
phase: 'memory_retrieval_rerank',
|
|
365
411
|
});
|
|
366
412
|
reranked = rerankResponse.value;
|
|
@@ -368,6 +414,7 @@ class AgentEngine {
|
|
|
368
414
|
reranked = merged;
|
|
369
415
|
}
|
|
370
416
|
} catch (error) {
|
|
417
|
+
if (isAbortError(error, signal)) throw error;
|
|
371
418
|
console.warn('[Memory] Retrieval enhancement failed:', error.message);
|
|
372
419
|
plan = null;
|
|
373
420
|
merged = initial;
|
|
@@ -406,10 +453,8 @@ class AgentEngine {
|
|
|
406
453
|
providerName,
|
|
407
454
|
model,
|
|
408
455
|
memoryManager,
|
|
456
|
+
signal = null,
|
|
409
457
|
}) {
|
|
410
|
-
const ids = chunks.map((c) => c.id);
|
|
411
|
-
memoryManager.markChunksExtracted?.(ids, { success: true });
|
|
412
|
-
|
|
413
458
|
const consolidationSchema = JSON.stringify({
|
|
414
459
|
memory_candidates: [{
|
|
415
460
|
memory: 'Concise standalone fact.',
|
|
@@ -430,6 +475,7 @@ class AgentEngine {
|
|
|
430
475
|
|
|
431
476
|
for (const chunk of chunks) {
|
|
432
477
|
try {
|
|
478
|
+
throwIfAborted(signal, 'Memory extraction aborted.');
|
|
433
479
|
const result = await this.requestStructuredJson({
|
|
434
480
|
provider,
|
|
435
481
|
providerName,
|
|
@@ -446,6 +492,7 @@ class AgentEngine {
|
|
|
446
492
|
maxTokens: 800,
|
|
447
493
|
normalize: (raw) => normalizeMemoryCandidates(raw?.memory_candidates || []),
|
|
448
494
|
fallback: [],
|
|
495
|
+
telemetry: { userId, agentId, signal },
|
|
449
496
|
phase: 'document_extraction',
|
|
450
497
|
});
|
|
451
498
|
|
|
@@ -457,10 +504,13 @@ class AgentEngine {
|
|
|
457
504
|
trustLevel: 'external_source',
|
|
458
505
|
sourceChunkMemoryId: chunk.id,
|
|
459
506
|
},
|
|
507
|
+
signal,
|
|
460
508
|
});
|
|
461
509
|
}
|
|
510
|
+
memoryManager.markChunksExtracted?.([chunk.id], { success: true });
|
|
462
511
|
} catch (err) {
|
|
463
512
|
memoryManager.markChunksExtracted?.([chunk.id], { success: false });
|
|
513
|
+
if (isAbortError(err, signal)) throw err;
|
|
464
514
|
console.warn('[Memory] Document chunk extraction failed:', err.message);
|
|
465
515
|
}
|
|
466
516
|
}
|
|
@@ -523,6 +573,7 @@ class AgentEngine {
|
|
|
523
573
|
? deliverableResult.artifacts.slice(0, 6)
|
|
524
574
|
: [],
|
|
525
575
|
},
|
|
576
|
+
signal: this.getRunMeta(runId)?.abortController?.signal || null,
|
|
526
577
|
},
|
|
527
578
|
);
|
|
528
579
|
} catch (error) {
|
|
@@ -766,7 +817,7 @@ class AgentEngine {
|
|
|
766
817
|
phase: 'completion_decision',
|
|
767
818
|
});
|
|
768
819
|
return {
|
|
769
|
-
decision: response.value,
|
|
820
|
+
decision: enforceTerminalReplyDecision(response.value, lastReply),
|
|
770
821
|
usage: response.usage,
|
|
771
822
|
raw: response.raw,
|
|
772
823
|
};
|
|
@@ -931,11 +982,12 @@ class AgentEngine {
|
|
|
931
982
|
}
|
|
932
983
|
];
|
|
933
984
|
|
|
934
|
-
const response = await
|
|
935
|
-
provider.chat(promptMessages, [], {
|
|
985
|
+
const response = await runAbortableModelCall(
|
|
986
|
+
(signal) => provider.chat(promptMessages, [], {
|
|
936
987
|
model,
|
|
937
988
|
maxTokens: 800,
|
|
938
989
|
reasoningEffort: this.getReasoningEffort(providerName, options),
|
|
990
|
+
signal,
|
|
939
991
|
}),
|
|
940
992
|
options,
|
|
941
993
|
'Conversation state refresh',
|
|
@@ -966,6 +1018,7 @@ class AgentEngine {
|
|
|
966
1018
|
agentId: options.agentId || null,
|
|
967
1019
|
conversationId,
|
|
968
1020
|
runId,
|
|
1021
|
+
signal: options.signal,
|
|
969
1022
|
},
|
|
970
1023
|
);
|
|
971
1024
|
const { invalidateSystemPromptCache } = require('../systemPrompt');
|
|
@@ -982,8 +1035,8 @@ class AgentEngine {
|
|
|
982
1035
|
return executeToolImpl(this, toolName, args, context);
|
|
983
1036
|
}
|
|
984
1037
|
|
|
985
|
-
isReadOnlyToolCall(toolCall) {
|
|
986
|
-
return isReadOnlyToolCallImpl(
|
|
1038
|
+
isReadOnlyToolCall(toolCall, toolDefinition = null) {
|
|
1039
|
+
return isReadOnlyToolCallImpl(toolCall, toolDefinition);
|
|
987
1040
|
}
|
|
988
1041
|
|
|
989
1042
|
async executeReadOnlyBatch(toolCalls, context = {}) {
|
|
@@ -1172,7 +1225,8 @@ class AgentEngine {
|
|
|
1172
1225
|
&& toolExecutions.length === 0
|
|
1173
1226
|
&& failedStepCount === 0
|
|
1174
1227
|
&& !messagingSent
|
|
1175
|
-
&& Boolean(String(lastReply || '').trim())
|
|
1228
|
+
&& Boolean(String(lastReply || '').trim())
|
|
1229
|
+
&& !isDeferredWorkReply(lastReply);
|
|
1176
1230
|
}
|
|
1177
1231
|
|
|
1178
1232
|
getMessagingRetryLimit(maxIterations) {
|
|
@@ -1291,15 +1345,37 @@ class AgentEngine {
|
|
|
1291
1345
|
}
|
|
1292
1346
|
|
|
1293
1347
|
async runWithModel(userId, userMessage, options = {}, _modelOverride = null) {
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1348
|
+
if (this.shuttingDown || this.lifecycleAbortController.signal.aborted) {
|
|
1349
|
+
const error = createAbortError(
|
|
1350
|
+
this.lifecycleAbortController.signal,
|
|
1351
|
+
'Agent engine is shutting down.',
|
|
1352
|
+
);
|
|
1353
|
+
error.code = error.code || 'AGENT_ENGINE_SHUTTING_DOWN';
|
|
1354
|
+
throw error;
|
|
1355
|
+
}
|
|
1356
|
+
const runPromise = runConversation(this, userId, userMessage, options, _modelOverride);
|
|
1357
|
+
this.activeRunPromises.add(runPromise);
|
|
1358
|
+
try {
|
|
1359
|
+
return await runPromise;
|
|
1360
|
+
} finally {
|
|
1361
|
+
this.activeRunPromises.delete(runPromise);
|
|
1362
|
+
}
|
|
1299
1363
|
}
|
|
1300
1364
|
|
|
1301
1365
|
async spawnSubagent(userId, parentRunId, task, options = {}) {
|
|
1302
|
-
|
|
1366
|
+
if (this.shuttingDown || this.lifecycleAbortController.signal.aborted) {
|
|
1367
|
+
throw createAbortError(this.lifecycleAbortController.signal, 'Agent engine is shutting down.');
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
let parentRunMeta = this.getRunMeta(parentRunId);
|
|
1371
|
+
if (
|
|
1372
|
+
!parentRunMeta
|
|
1373
|
+
|| parentRunMeta.userId !== userId
|
|
1374
|
+
|| parentRunMeta.status !== 'running'
|
|
1375
|
+
|| parentRunMeta.aborted === true
|
|
1376
|
+
) {
|
|
1377
|
+
return { error: 'The parent run is no longer active, so a sub-agent cannot be started.' };
|
|
1378
|
+
}
|
|
1303
1379
|
const parentDepth = Math.max(0, Number(parentRunMeta?.subagentDepth) || 0);
|
|
1304
1380
|
if (parentDepth >= 1) {
|
|
1305
1381
|
return {
|
|
@@ -1309,24 +1385,77 @@ class AgentEngine {
|
|
|
1309
1385
|
|
|
1310
1386
|
const aiSettings = getAiSettings(userId, options.agentId || null);
|
|
1311
1387
|
const maxSubagentsPerRun = Math.max(1, Number(aiSettings.subagent_max_children_per_run) || 10);
|
|
1312
|
-
const
|
|
1313
|
-
.filter((record) => record.parentRunId === parentRunId);
|
|
1314
|
-
if (
|
|
1388
|
+
const countExistingSubagents = () => Array.from(this.subagents.values())
|
|
1389
|
+
.filter((record) => record.parentRunId === parentRunId).length;
|
|
1390
|
+
if (countExistingSubagents() >= maxSubagentsPerRun) {
|
|
1315
1391
|
return {
|
|
1316
|
-
error: `This run has already spawned ${
|
|
1392
|
+
error: `This run has already spawned ${countExistingSubagents()} sub-agents. The limit for one run is ${maxSubagentsPerRun}.`,
|
|
1317
1393
|
};
|
|
1318
1394
|
}
|
|
1319
1395
|
|
|
1320
1396
|
const handle = uuidv4();
|
|
1321
1397
|
const childRunId = uuidv4();
|
|
1322
1398
|
let relevantMemories = [];
|
|
1399
|
+
const startupLink = createLinkedAbortController([
|
|
1400
|
+
this.lifecycleAbortController.signal,
|
|
1401
|
+
parentRunMeta.abortController?.signal,
|
|
1402
|
+
options.signal,
|
|
1403
|
+
]);
|
|
1404
|
+
let memoryRecall = null;
|
|
1323
1405
|
try {
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1406
|
+
throwIfAborted(startupLink.signal, 'Sub-agent startup was aborted.');
|
|
1407
|
+
memoryRecall = this.memoryManager
|
|
1408
|
+
? runWithAbortTimeout(
|
|
1409
|
+
(signal) => this.memoryManager.recallMemory(userId, task, 4, {
|
|
1410
|
+
agentId: options.agentId || null,
|
|
1411
|
+
signal,
|
|
1412
|
+
}),
|
|
1413
|
+
{
|
|
1414
|
+
label: 'Sub-agent memory recall',
|
|
1415
|
+
signal: startupLink.signal,
|
|
1416
|
+
},
|
|
1417
|
+
)
|
|
1418
|
+
: Promise.resolve([]);
|
|
1419
|
+
this.subagentStartupTasks.add(memoryRecall);
|
|
1420
|
+
relevantMemories = await memoryRecall;
|
|
1421
|
+
throwIfAborted(startupLink.signal, 'Sub-agent startup was aborted.');
|
|
1422
|
+
} catch (error) {
|
|
1423
|
+
if (isAbortError(error, startupLink.signal)) throw error;
|
|
1424
|
+
console.warn('[AgentEngine] Sub-agent memory recall failed:', error?.message || error);
|
|
1425
|
+
} finally {
|
|
1426
|
+
if (memoryRecall) this.subagentStartupTasks.delete(memoryRecall);
|
|
1427
|
+
startupLink.cleanup();
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
parentRunMeta = this.getRunMeta(parentRunId);
|
|
1431
|
+
if (
|
|
1432
|
+
this.shuttingDown
|
|
1433
|
+
|| this.lifecycleAbortController.signal.aborted
|
|
1434
|
+
|| !parentRunMeta
|
|
1435
|
+
|| parentRunMeta.userId !== userId
|
|
1436
|
+
|| parentRunMeta.status !== 'running'
|
|
1437
|
+
|| parentRunMeta.aborted === true
|
|
1438
|
+
) {
|
|
1439
|
+
throw createAbortError(
|
|
1440
|
+
this.lifecycleAbortController.signal.aborted
|
|
1441
|
+
? this.lifecycleAbortController.signal
|
|
1442
|
+
: parentRunMeta?.abortController?.signal,
|
|
1443
|
+
'The parent run stopped before the sub-agent could start.',
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
const currentSubagentCount = countExistingSubagents();
|
|
1447
|
+
if (currentSubagentCount >= maxSubagentsPerRun) {
|
|
1448
|
+
return {
|
|
1449
|
+
error: `This run has already spawned ${currentSubagentCount} sub-agents. The limit for one run is ${maxSubagentsPerRun}.`,
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
const childLink = createLinkedAbortController([
|
|
1454
|
+
this.lifecycleAbortController.signal,
|
|
1455
|
+
parentRunMeta.abortController?.signal,
|
|
1456
|
+
options.signal,
|
|
1457
|
+
]);
|
|
1458
|
+
throwIfAborted(childLink.signal, 'Sub-agent startup was aborted.');
|
|
1330
1459
|
const subEngine = new AgentEngine(this.io, {
|
|
1331
1460
|
app: options.app || this.app,
|
|
1332
1461
|
browserController: this.browserController,
|
|
@@ -1372,6 +1501,8 @@ class AgentEngine {
|
|
|
1372
1501
|
error: null,
|
|
1373
1502
|
engine: subEngine,
|
|
1374
1503
|
promise: null,
|
|
1504
|
+
settled: false,
|
|
1505
|
+
deleteWhenSettled: false,
|
|
1375
1506
|
};
|
|
1376
1507
|
this.subagents.set(handle, record);
|
|
1377
1508
|
this.emit(userId, 'run:subagent', {
|
|
@@ -1395,9 +1526,11 @@ class AgentEngine {
|
|
|
1395
1526
|
agentId: options.agentId || null,
|
|
1396
1527
|
subagentDepth: parentDepth + 1,
|
|
1397
1528
|
disallowedToolNames: ['spawn_subagent'],
|
|
1529
|
+
signal: childLink.signal,
|
|
1398
1530
|
},
|
|
1399
1531
|
options.model || null
|
|
1400
1532
|
);
|
|
1533
|
+
if (record.status === 'cancelled' || childLink.signal.aborted) return record;
|
|
1401
1534
|
record.status = result.status || 'completed';
|
|
1402
1535
|
let structured = null;
|
|
1403
1536
|
try {
|
|
@@ -1425,16 +1558,23 @@ class AgentEngine {
|
|
|
1425
1558
|
});
|
|
1426
1559
|
return record;
|
|
1427
1560
|
} catch (err) {
|
|
1428
|
-
record.status
|
|
1429
|
-
record.
|
|
1561
|
+
const cancelled = record.status === 'cancelled' || isAbortError(err, childLink.signal);
|
|
1562
|
+
record.status = cancelled ? 'cancelled' : 'failed';
|
|
1563
|
+
record.error = cancelled ? null : (err?.message || String(err));
|
|
1430
1564
|
this.emit(userId, 'run:subagent', {
|
|
1431
1565
|
runId: parentRunId,
|
|
1432
1566
|
handle,
|
|
1433
1567
|
childRunId,
|
|
1434
|
-
status:
|
|
1435
|
-
error:
|
|
1568
|
+
status: record.status,
|
|
1569
|
+
error: record.error,
|
|
1436
1570
|
});
|
|
1437
|
-
|
|
1571
|
+
return record;
|
|
1572
|
+
} finally {
|
|
1573
|
+
record.settled = true;
|
|
1574
|
+
childLink.cleanup();
|
|
1575
|
+
if (record.deleteWhenSettled && this.subagents.get(handle) === record) {
|
|
1576
|
+
this.subagents.delete(handle);
|
|
1577
|
+
}
|
|
1438
1578
|
}
|
|
1439
1579
|
})();
|
|
1440
1580
|
|
|
@@ -1571,21 +1711,43 @@ class AgentEngine {
|
|
|
1571
1711
|
}));
|
|
1572
1712
|
}
|
|
1573
1713
|
|
|
1574
|
-
cleanupSubagentsForRun(parentRunId, options = {}) {
|
|
1575
|
-
if (!parentRunId) return;
|
|
1714
|
+
async cleanupSubagentsForRun(parentRunId, options = {}) {
|
|
1715
|
+
if (!parentRunId) return { cancelled: 0, timedOut: 0 };
|
|
1576
1716
|
const cancelRunning = options.cancelRunning !== false;
|
|
1577
|
-
|
|
1717
|
+
const shutdowns = [];
|
|
1718
|
+
const records = [];
|
|
1719
|
+
for (const [handle, record] of Array.from(this.subagents.entries())) {
|
|
1578
1720
|
if (record.parentRunId !== parentRunId) continue;
|
|
1721
|
+
records.push([handle, record]);
|
|
1722
|
+
record.deleteWhenSettled = true;
|
|
1579
1723
|
if (cancelRunning && record.status === 'running') {
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
record.
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1724
|
+
record.status = 'cancelled';
|
|
1725
|
+
this.emit(record.userId, 'run:subagent', {
|
|
1726
|
+
runId: record.parentRunId,
|
|
1727
|
+
handle,
|
|
1728
|
+
childRunId: record.childRunId,
|
|
1729
|
+
status: 'cancelled',
|
|
1730
|
+
});
|
|
1731
|
+
shutdowns.push(Promise.resolve(record.engine?.shutdown?.({
|
|
1732
|
+
reason: 'Parent run finished before the sub-agent completed.',
|
|
1733
|
+
timeoutMs: Math.max(100, Number(options.timeoutMs) || 10000),
|
|
1734
|
+
})));
|
|
1735
|
+
} else if (record.status === 'running') {
|
|
1736
|
+
record.deleteWhenSettled = false;
|
|
1586
1737
|
}
|
|
1587
|
-
this.subagents.delete(handle);
|
|
1588
1738
|
}
|
|
1739
|
+
const results = await Promise.allSettled(shutdowns);
|
|
1740
|
+
for (const [handle, record] of records) {
|
|
1741
|
+
if ((record.settled || !record.promise) && this.subagents.get(handle) === record) {
|
|
1742
|
+
this.subagents.delete(handle);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
return {
|
|
1746
|
+
cancelled: shutdowns.length,
|
|
1747
|
+
timedOut: results.filter((result) => (
|
|
1748
|
+
result.status === 'fulfilled' && result.value?.timedOut === true
|
|
1749
|
+
)).length,
|
|
1750
|
+
};
|
|
1589
1751
|
}
|
|
1590
1752
|
|
|
1591
1753
|
async waitForSubagent(handle, options = {}) {
|
|
@@ -1648,7 +1810,6 @@ class AgentEngine {
|
|
|
1648
1810
|
};
|
|
1649
1811
|
}
|
|
1650
1812
|
|
|
1651
|
-
record.engine?.abort(record.childRunId);
|
|
1652
1813
|
record.status = 'cancelled';
|
|
1653
1814
|
this.emit(record.userId, 'run:subagent', {
|
|
1654
1815
|
runId: record.parentRunId,
|
|
@@ -1657,7 +1818,15 @@ class AgentEngine {
|
|
|
1657
1818
|
status: 'cancelled',
|
|
1658
1819
|
});
|
|
1659
1820
|
|
|
1660
|
-
|
|
1821
|
+
const shutdown = await record.engine?.shutdown?.({
|
|
1822
|
+
reason: 'Sub-agent cancelled by its parent run.',
|
|
1823
|
+
timeoutMs: Math.max(100, Number(options.timeoutMs) || 10000),
|
|
1824
|
+
});
|
|
1825
|
+
return {
|
|
1826
|
+
handle,
|
|
1827
|
+
status: 'cancelled',
|
|
1828
|
+
timedOut: shutdown?.timedOut === true,
|
|
1829
|
+
};
|
|
1661
1830
|
}
|
|
1662
1831
|
|
|
1663
1832
|
interruptRun(runId, reason = 'Server shutting down while run was in progress.') {
|
|
@@ -1705,21 +1874,26 @@ class AgentEngine {
|
|
|
1705
1874
|
}
|
|
1706
1875
|
}
|
|
1707
1876
|
|
|
1708
|
-
stopRun(runId) {
|
|
1877
|
+
stopRun(runId, reason = 'Stopped by request.') {
|
|
1878
|
+
const stopReason = String(reason || 'Stopped by request.').slice(0, 1000);
|
|
1709
1879
|
const runMeta = this.activeRuns.get(runId);
|
|
1710
|
-
const persistedRun = runMeta || db.prepare(
|
|
1880
|
+
const persistedRun = runMeta || db.prepare(
|
|
1881
|
+
'SELECT user_id AS userId, status FROM agent_runs WHERE id = ?',
|
|
1882
|
+
).get(runId);
|
|
1883
|
+
if (!persistedRun || TERMINAL_STATUSES.has(persistedRun.status)) return false;
|
|
1711
1884
|
if (persistedRun?.userId != null) {
|
|
1712
|
-
requestRunControl(runId, persistedRun.userId, 'stop',
|
|
1885
|
+
requestRunControl(runId, persistedRun.userId, 'stop', stopReason);
|
|
1713
1886
|
}
|
|
1714
1887
|
const delegatedChildren = db.prepare(
|
|
1715
1888
|
"SELECT child_run_id FROM agent_delegations WHERE parent_run_id = ? AND status = 'running'"
|
|
1716
1889
|
).all(runId);
|
|
1717
1890
|
if (runMeta) {
|
|
1718
1891
|
runMeta.status = 'stopped';
|
|
1892
|
+
runMeta.stopReason = stopReason;
|
|
1719
1893
|
runMeta.aborted = true;
|
|
1720
|
-
runMeta.abortController?.abort(
|
|
1894
|
+
runMeta.abortController?.abort(stopReason);
|
|
1721
1895
|
runMeta.resumeRun?.();
|
|
1722
|
-
this.emit(runMeta.userId, 'run:stopping', { runId });
|
|
1896
|
+
this.emit(runMeta.userId, 'run:stopping', { runId, reason: stopReason });
|
|
1723
1897
|
for (const pid of runMeta.toolPids) {
|
|
1724
1898
|
if (this.runtimeManager && typeof this.runtimeManager.killCommand === 'function') {
|
|
1725
1899
|
void this.runtimeManager.killCommand(runMeta.userId, pid, 'aborted');
|
|
@@ -1729,13 +1903,22 @@ class AgentEngine {
|
|
|
1729
1903
|
}
|
|
1730
1904
|
for (const child of delegatedChildren) {
|
|
1731
1905
|
if (child.child_run_id && child.child_run_id !== runId) {
|
|
1732
|
-
this.stopRun(child.child_run_id);
|
|
1906
|
+
this.stopRun(child.child_run_id, stopReason);
|
|
1733
1907
|
}
|
|
1734
1908
|
}
|
|
1735
1909
|
db.prepare(
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1910
|
+
`UPDATE agent_delegations
|
|
1911
|
+
SET status = 'stopped', error = COALESCE(NULLIF(error, ''), ?),
|
|
1912
|
+
updated_at = datetime('now'), completed_at = datetime('now')
|
|
1913
|
+
WHERE parent_run_id = ? AND status = 'running'`
|
|
1914
|
+
).run(stopReason, runId);
|
|
1915
|
+
closeRun(
|
|
1916
|
+
runId,
|
|
1917
|
+
'stopped',
|
|
1918
|
+
{ error: stopReason },
|
|
1919
|
+
['running', 'pausing', 'paused', 'resuming'],
|
|
1920
|
+
);
|
|
1921
|
+
return true;
|
|
1739
1922
|
}
|
|
1740
1923
|
|
|
1741
1924
|
pauseRun(runId, { userId, reason = '' } = {}) {
|
|
@@ -1777,15 +1960,17 @@ class AgentEngine {
|
|
|
1777
1960
|
return true;
|
|
1778
1961
|
}
|
|
1779
1962
|
|
|
1780
|
-
abort(runId, { userId } = {}) {
|
|
1963
|
+
abort(runId, { userId, reason = 'Stopped by request.' } = {}) {
|
|
1781
1964
|
if (!runId) return false;
|
|
1965
|
+
const runMeta = this.activeRuns.get(runId);
|
|
1966
|
+
const persistedRun = runMeta
|
|
1967
|
+
|| db.prepare('SELECT user_id AS userId, status FROM agent_runs WHERE id = ?').get(runId);
|
|
1968
|
+
if (!persistedRun || TERMINAL_STATUSES.has(persistedRun.status)) return false;
|
|
1782
1969
|
if (userId != null) {
|
|
1783
|
-
// Ownership gate
|
|
1784
|
-
|
|
1785
|
-
if (runMeta && Number(runMeta.userId) !== Number(userId)) return false;
|
|
1970
|
+
// Ownership gate applies to both active and persisted runs.
|
|
1971
|
+
if (Number(persistedRun.userId) !== Number(userId)) return false;
|
|
1786
1972
|
}
|
|
1787
|
-
this.stopRun(runId);
|
|
1788
|
-
return true;
|
|
1973
|
+
return this.stopRun(runId, reason);
|
|
1789
1974
|
}
|
|
1790
1975
|
|
|
1791
1976
|
abortAll(userId) {
|