neoagent 3.2.1-beta.1 → 3.2.1-beta.3
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 +358 -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 +20 -45
- 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 +21 -33
- package/server/services/ai/providers/githubCopilot.js +41 -20
- 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 +19 -27
- package/server/services/ai/providers/nvidia.js +10 -5
- package/server/services/ai/providers/ollama.js +111 -84
- 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/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 = {}) {
|
|
@@ -1119,6 +1172,15 @@ class AgentEngine {
|
|
|
1119
1172
|
|
|
1120
1173
|
await new Promise((resolve) => { runMeta.resumeRun = resolve; });
|
|
1121
1174
|
runMeta.resumeRun = null;
|
|
1175
|
+
if (runMeta.status === 'stopped' || runMeta.status === 'interrupted') {
|
|
1176
|
+
return { action: runMeta.status === 'interrupted' ? 'interrupt' : 'stop' };
|
|
1177
|
+
}
|
|
1178
|
+
const persistedStatus = db.prepare('SELECT status FROM agent_runs WHERE id = ?').get(runId)?.status;
|
|
1179
|
+
if (persistedStatus !== 'running') {
|
|
1180
|
+
const action = persistedStatus === 'interrupted' ? 'interrupt' : 'stop';
|
|
1181
|
+
runMeta.status = persistedStatus || 'stopped';
|
|
1182
|
+
return { action };
|
|
1183
|
+
}
|
|
1122
1184
|
runMeta.abortController = new AbortController();
|
|
1123
1185
|
runMeta.status = 'running';
|
|
1124
1186
|
this.startMessagingProgressSupervisor(runId);
|
|
@@ -1163,7 +1225,8 @@ class AgentEngine {
|
|
|
1163
1225
|
&& toolExecutions.length === 0
|
|
1164
1226
|
&& failedStepCount === 0
|
|
1165
1227
|
&& !messagingSent
|
|
1166
|
-
&& Boolean(String(lastReply || '').trim())
|
|
1228
|
+
&& Boolean(String(lastReply || '').trim())
|
|
1229
|
+
&& !isDeferredWorkReply(lastReply);
|
|
1167
1230
|
}
|
|
1168
1231
|
|
|
1169
1232
|
getMessagingRetryLimit(maxIterations) {
|
|
@@ -1282,15 +1345,37 @@ class AgentEngine {
|
|
|
1282
1345
|
}
|
|
1283
1346
|
|
|
1284
1347
|
async runWithModel(userId, userMessage, options = {}, _modelOverride = null) {
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
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
|
+
}
|
|
1290
1363
|
}
|
|
1291
1364
|
|
|
1292
1365
|
async spawnSubagent(userId, parentRunId, task, options = {}) {
|
|
1293
|
-
|
|
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
|
+
}
|
|
1294
1379
|
const parentDepth = Math.max(0, Number(parentRunMeta?.subagentDepth) || 0);
|
|
1295
1380
|
if (parentDepth >= 1) {
|
|
1296
1381
|
return {
|
|
@@ -1300,24 +1385,77 @@ class AgentEngine {
|
|
|
1300
1385
|
|
|
1301
1386
|
const aiSettings = getAiSettings(userId, options.agentId || null);
|
|
1302
1387
|
const maxSubagentsPerRun = Math.max(1, Number(aiSettings.subagent_max_children_per_run) || 10);
|
|
1303
|
-
const
|
|
1304
|
-
.filter((record) => record.parentRunId === parentRunId);
|
|
1305
|
-
if (
|
|
1388
|
+
const countExistingSubagents = () => Array.from(this.subagents.values())
|
|
1389
|
+
.filter((record) => record.parentRunId === parentRunId).length;
|
|
1390
|
+
if (countExistingSubagents() >= maxSubagentsPerRun) {
|
|
1306
1391
|
return {
|
|
1307
|
-
error: `This run has already spawned ${
|
|
1392
|
+
error: `This run has already spawned ${countExistingSubagents()} sub-agents. The limit for one run is ${maxSubagentsPerRun}.`,
|
|
1308
1393
|
};
|
|
1309
1394
|
}
|
|
1310
1395
|
|
|
1311
1396
|
const handle = uuidv4();
|
|
1312
1397
|
const childRunId = uuidv4();
|
|
1313
1398
|
let relevantMemories = [];
|
|
1399
|
+
const startupLink = createLinkedAbortController([
|
|
1400
|
+
this.lifecycleAbortController.signal,
|
|
1401
|
+
parentRunMeta.abortController?.signal,
|
|
1402
|
+
options.signal,
|
|
1403
|
+
]);
|
|
1404
|
+
let memoryRecall = null;
|
|
1314
1405
|
try {
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
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.');
|
|
1321
1459
|
const subEngine = new AgentEngine(this.io, {
|
|
1322
1460
|
app: options.app || this.app,
|
|
1323
1461
|
browserController: this.browserController,
|
|
@@ -1363,6 +1501,8 @@ class AgentEngine {
|
|
|
1363
1501
|
error: null,
|
|
1364
1502
|
engine: subEngine,
|
|
1365
1503
|
promise: null,
|
|
1504
|
+
settled: false,
|
|
1505
|
+
deleteWhenSettled: false,
|
|
1366
1506
|
};
|
|
1367
1507
|
this.subagents.set(handle, record);
|
|
1368
1508
|
this.emit(userId, 'run:subagent', {
|
|
@@ -1386,9 +1526,11 @@ class AgentEngine {
|
|
|
1386
1526
|
agentId: options.agentId || null,
|
|
1387
1527
|
subagentDepth: parentDepth + 1,
|
|
1388
1528
|
disallowedToolNames: ['spawn_subagent'],
|
|
1529
|
+
signal: childLink.signal,
|
|
1389
1530
|
},
|
|
1390
1531
|
options.model || null
|
|
1391
1532
|
);
|
|
1533
|
+
if (record.status === 'cancelled' || childLink.signal.aborted) return record;
|
|
1392
1534
|
record.status = result.status || 'completed';
|
|
1393
1535
|
let structured = null;
|
|
1394
1536
|
try {
|
|
@@ -1416,16 +1558,23 @@ class AgentEngine {
|
|
|
1416
1558
|
});
|
|
1417
1559
|
return record;
|
|
1418
1560
|
} catch (err) {
|
|
1419
|
-
record.status
|
|
1420
|
-
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));
|
|
1421
1564
|
this.emit(userId, 'run:subagent', {
|
|
1422
1565
|
runId: parentRunId,
|
|
1423
1566
|
handle,
|
|
1424
1567
|
childRunId,
|
|
1425
|
-
status:
|
|
1426
|
-
error:
|
|
1568
|
+
status: record.status,
|
|
1569
|
+
error: record.error,
|
|
1427
1570
|
});
|
|
1428
|
-
|
|
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
|
+
}
|
|
1429
1578
|
}
|
|
1430
1579
|
})();
|
|
1431
1580
|
|
|
@@ -1562,21 +1711,43 @@ class AgentEngine {
|
|
|
1562
1711
|
}));
|
|
1563
1712
|
}
|
|
1564
1713
|
|
|
1565
|
-
cleanupSubagentsForRun(parentRunId, options = {}) {
|
|
1566
|
-
if (!parentRunId) return;
|
|
1714
|
+
async cleanupSubagentsForRun(parentRunId, options = {}) {
|
|
1715
|
+
if (!parentRunId) return { cancelled: 0, timedOut: 0 };
|
|
1567
1716
|
const cancelRunning = options.cancelRunning !== false;
|
|
1568
|
-
|
|
1717
|
+
const shutdowns = [];
|
|
1718
|
+
const records = [];
|
|
1719
|
+
for (const [handle, record] of Array.from(this.subagents.entries())) {
|
|
1569
1720
|
if (record.parentRunId !== parentRunId) continue;
|
|
1721
|
+
records.push([handle, record]);
|
|
1722
|
+
record.deleteWhenSettled = true;
|
|
1570
1723
|
if (cancelRunning && record.status === 'running') {
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
record.
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
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;
|
|
1737
|
+
}
|
|
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);
|
|
1577
1743
|
}
|
|
1578
|
-
this.subagents.delete(handle);
|
|
1579
1744
|
}
|
|
1745
|
+
return {
|
|
1746
|
+
cancelled: shutdowns.length,
|
|
1747
|
+
timedOut: results.filter((result) => (
|
|
1748
|
+
result.status === 'fulfilled' && result.value?.timedOut === true
|
|
1749
|
+
)).length,
|
|
1750
|
+
};
|
|
1580
1751
|
}
|
|
1581
1752
|
|
|
1582
1753
|
async waitForSubagent(handle, options = {}) {
|
|
@@ -1639,7 +1810,6 @@ class AgentEngine {
|
|
|
1639
1810
|
};
|
|
1640
1811
|
}
|
|
1641
1812
|
|
|
1642
|
-
record.engine?.abort(record.childRunId);
|
|
1643
1813
|
record.status = 'cancelled';
|
|
1644
1814
|
this.emit(record.userId, 'run:subagent', {
|
|
1645
1815
|
runId: record.parentRunId,
|
|
@@ -1648,7 +1818,15 @@ class AgentEngine {
|
|
|
1648
1818
|
status: 'cancelled',
|
|
1649
1819
|
});
|
|
1650
1820
|
|
|
1651
|
-
|
|
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
|
+
};
|
|
1652
1830
|
}
|
|
1653
1831
|
|
|
1654
1832
|
interruptRun(runId, reason = 'Server shutting down while run was in progress.') {
|
|
@@ -1665,6 +1843,7 @@ class AgentEngine {
|
|
|
1665
1843
|
runMeta.stopReason = reason;
|
|
1666
1844
|
runMeta.aborted = true;
|
|
1667
1845
|
runMeta.abortController?.abort(reason);
|
|
1846
|
+
runMeta.resumeRun?.();
|
|
1668
1847
|
this.emit(runMeta.userId, 'run:stopping', { runId });
|
|
1669
1848
|
for (const pid of runMeta.toolPids) {
|
|
1670
1849
|
if (this.runtimeManager && typeof this.runtimeManager.killCommand === 'function') {
|
|
@@ -1695,20 +1874,26 @@ class AgentEngine {
|
|
|
1695
1874
|
}
|
|
1696
1875
|
}
|
|
1697
1876
|
|
|
1698
|
-
stopRun(runId) {
|
|
1877
|
+
stopRun(runId, reason = 'Stopped by request.') {
|
|
1878
|
+
const stopReason = String(reason || 'Stopped by request.').slice(0, 1000);
|
|
1699
1879
|
const runMeta = this.activeRuns.get(runId);
|
|
1700
|
-
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;
|
|
1701
1884
|
if (persistedRun?.userId != null) {
|
|
1702
|
-
requestRunControl(runId, persistedRun.userId, 'stop',
|
|
1885
|
+
requestRunControl(runId, persistedRun.userId, 'stop', stopReason);
|
|
1703
1886
|
}
|
|
1704
1887
|
const delegatedChildren = db.prepare(
|
|
1705
1888
|
"SELECT child_run_id FROM agent_delegations WHERE parent_run_id = ? AND status = 'running'"
|
|
1706
1889
|
).all(runId);
|
|
1707
1890
|
if (runMeta) {
|
|
1708
1891
|
runMeta.status = 'stopped';
|
|
1892
|
+
runMeta.stopReason = stopReason;
|
|
1709
1893
|
runMeta.aborted = true;
|
|
1710
|
-
runMeta.abortController?.abort(
|
|
1711
|
-
|
|
1894
|
+
runMeta.abortController?.abort(stopReason);
|
|
1895
|
+
runMeta.resumeRun?.();
|
|
1896
|
+
this.emit(runMeta.userId, 'run:stopping', { runId, reason: stopReason });
|
|
1712
1897
|
for (const pid of runMeta.toolPids) {
|
|
1713
1898
|
if (this.runtimeManager && typeof this.runtimeManager.killCommand === 'function') {
|
|
1714
1899
|
void this.runtimeManager.killCommand(runMeta.userId, pid, 'aborted');
|
|
@@ -1718,13 +1903,22 @@ class AgentEngine {
|
|
|
1718
1903
|
}
|
|
1719
1904
|
for (const child of delegatedChildren) {
|
|
1720
1905
|
if (child.child_run_id && child.child_run_id !== runId) {
|
|
1721
|
-
this.stopRun(child.child_run_id);
|
|
1906
|
+
this.stopRun(child.child_run_id, stopReason);
|
|
1722
1907
|
}
|
|
1723
1908
|
}
|
|
1724
1909
|
db.prepare(
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
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;
|
|
1728
1922
|
}
|
|
1729
1923
|
|
|
1730
1924
|
pauseRun(runId, { userId, reason = '' } = {}) {
|
|
@@ -1766,15 +1960,17 @@ class AgentEngine {
|
|
|
1766
1960
|
return true;
|
|
1767
1961
|
}
|
|
1768
1962
|
|
|
1769
|
-
abort(runId, { userId } = {}) {
|
|
1963
|
+
abort(runId, { userId, reason = 'Stopped by request.' } = {}) {
|
|
1770
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;
|
|
1771
1969
|
if (userId != null) {
|
|
1772
|
-
// Ownership gate
|
|
1773
|
-
|
|
1774
|
-
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;
|
|
1775
1972
|
}
|
|
1776
|
-
this.stopRun(runId);
|
|
1777
|
-
return true;
|
|
1973
|
+
return this.stopRun(runId, reason);
|
|
1778
1974
|
}
|
|
1779
1975
|
|
|
1780
1976
|
abortAll(userId) {
|