neoagent 2.4.1-beta.8 → 2.4.1
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/.env.example +33 -3
- package/LICENSE +111 -56
- package/README.md +8 -3
- package/docs/configuration.md +8 -0
- package/docs/getting-started.md +9 -3
- package/docs/index.md +4 -0
- package/extensions/chrome-browser/background.mjs +45 -7
- package/extensions/chrome-browser/icons/icon128.png +0 -0
- package/extensions/chrome-browser/icons/icon16.png +0 -0
- package/extensions/chrome-browser/icons/icon48.png +0 -0
- package/extensions/chrome-browser/icons/logo.svg +39 -8
- package/extensions/chrome-browser/manifest.json +3 -2
- package/extensions/chrome-browser/popup.html +5 -1
- package/extensions/chrome-browser/popup.js +15 -2
- package/flutter_app/android/app/src/main/AndroidManifest.xml +2 -1
- package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/flutter_app/assets/branding/app_icon_1024.png +0 -0
- package/flutter_app/assets/branding/app_icon_128.png +0 -0
- package/flutter_app/assets/branding/app_icon_192.png +0 -0
- package/flutter_app/assets/branding/app_icon_256.png +0 -0
- package/flutter_app/assets/branding/app_icon_32.png +0 -0
- package/flutter_app/assets/branding/app_icon_512.png +0 -0
- package/flutter_app/assets/branding/app_icon_64.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_1024.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_128.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_192.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_256.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_32.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_512.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_64.png +0 -0
- package/flutter_app/assets/branding/onboarding_intro.mp4 +0 -0
- package/flutter_app/assets/branding/tray_icon_light_template.png +0 -0
- package/flutter_app/assets/branding/tray_icon_template.png +0 -0
- package/flutter_app/lib/features/location/location_service.dart +3 -0
- package/flutter_app/lib/features/onboarding/onboarding_chrome.dart +391 -382
- package/flutter_app/lib/features/onboarding/onboarding_companion_step.dart +743 -0
- package/flutter_app/lib/features/onboarding/onboarding_messaging_step.dart +18 -16
- package/flutter_app/lib/features/onboarding/onboarding_model_step.dart +19 -18
- package/flutter_app/lib/features/onboarding/onboarding_shell.dart +8 -1
- package/flutter_app/lib/features/onboarding/onboarding_video_step.dart +16 -13
- package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +17 -13
- package/flutter_app/lib/main.dart +3 -0
- package/flutter_app/lib/main_account_settings.dart +10 -34
- package/flutter_app/lib/main_admin.dart +14 -1
- package/flutter_app/lib/main_app_shell.dart +377 -340
- package/flutter_app/lib/main_chat.dart +707 -227
- package/flutter_app/lib/main_controller.dart +338 -46
- package/flutter_app/lib/main_devices.dart +851 -122
- package/flutter_app/lib/main_integrations.dart +255 -6
- package/flutter_app/lib/main_launcher.dart +1 -1
- package/flutter_app/lib/main_model_picker.dart +685 -0
- package/flutter_app/lib/main_models.dart +212 -0
- package/flutter_app/lib/main_navigation.dart +1 -9
- package/flutter_app/lib/main_operations.dart +1417 -614
- package/flutter_app/lib/main_settings.dart +764 -887
- package/flutter_app/lib/main_shared.dart +971 -443
- package/flutter_app/lib/main_spacing.dart +4 -4
- package/flutter_app/lib/main_theme.dart +22 -14
- package/flutter_app/lib/main_unified.dart +5 -72
- package/flutter_app/lib/src/android_apk_drop_zone.dart +24 -0
- package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +13 -0
- package/flutter_app/lib/src/android_apk_drop_zone_web.dart +219 -0
- package/flutter_app/lib/src/backend_client.dart +79 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +56 -7
- package/flutter_app/lib/src/desktop_companion_io.dart +77 -1
- package/flutter_app/lib/src/desktop_native_bridge.dart +13 -0
- package/flutter_app/lib/src/security/password_strength.dart +84 -0
- package/flutter_app/lib/src/stream_renderer.dart +205 -35
- package/flutter_app/lib/src/theme/palette.dart +76 -34
- package/flutter_app/linux/runner/resources/app_icon.png +0 -0
- package/flutter_app/macos/Runner/AppDelegate.swift +44 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
- package/flutter_app/pubspec.lock +2 -2
- package/flutter_app/pubspec.yaml +7 -1
- package/flutter_app/tool/branding_source/neoagent-icon-1024.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-128.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-16.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-180.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-192.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-256.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-32.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-48.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-512.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-64.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon.svg +43 -0
- package/flutter_app/tool/generate_desktop_branding.py +154 -152
- package/flutter_app/web/favicon.png +0 -0
- package/flutter_app/web/favicon.svg +40 -9
- package/flutter_app/web/favicon_light.svg +43 -0
- package/flutter_app/web/icons/Icon-192-light.png +0 -0
- package/flutter_app/web/icons/Icon-192.png +0 -0
- package/flutter_app/web/icons/Icon-512-light.png +0 -0
- package/flutter_app/web/icons/Icon-512.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-192-light.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-512-light.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
- package/flutter_app/windows/runner/main.cpp +7 -1
- package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
- package/lib/manager.js +445 -83
- package/package.json +17 -3
- package/runtime/paths.js +3 -1
- package/server/admin/access.js +198 -0
- package/server/admin/admin.css +268 -0
- package/server/admin/admin.js +348 -0
- package/server/admin/analytics.js +128 -0
- package/server/admin/index.html +1015 -0
- package/server/admin/login.html +290 -0
- package/server/admin/logo.svg +43 -0
- package/server/admin/sql.js +134 -0
- package/server/admin/users.js +147 -0
- package/server/config/origins.js +3 -1
- package/server/db/database.js +92 -0
- package/server/db/ftsQuery.js +27 -0
- package/server/http/routes.js +1 -0
- package/server/http/static.js +23 -6
- package/server/index.js +1 -1
- package/server/middleware/adminAuth.js +48 -0
- package/server/middleware/auth.js +1 -40
- package/server/public/.last_build_id +1 -1
- package/server/public/app_icon.png +0 -0
- package/server/public/assets/AssetManifest.bin +1 -1
- package/server/public/assets/AssetManifest.bin.json +1 -1
- package/server/public/assets/assets/branding/app_icon_1024.png +0 -0
- package/server/public/assets/assets/branding/app_icon_256.png +0 -0
- package/server/public/assets/assets/branding/app_icon_512.png +0 -0
- package/server/public/assets/assets/branding/app_icon_light_1024.png +0 -0
- package/server/public/assets/assets/branding/app_icon_light_256.png +0 -0
- package/server/public/assets/assets/branding/app_icon_light_512.png +0 -0
- package/server/public/assets/assets/branding/onboarding_intro.mp4 +0 -0
- package/server/public/assets/assets/branding/tray_icon_light_template.png +0 -0
- package/server/public/assets/assets/branding/tray_icon_template.png +0 -0
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/assets/web/icons/Icon-192.png +0 -0
- package/server/public/favicon.png +0 -0
- package/server/public/favicon.svg +40 -9
- package/server/public/favicon_light.svg +43 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/icons/Icon-192-light.png +0 -0
- package/server/public/icons/Icon-192.png +0 -0
- package/server/public/icons/Icon-512-light.png +0 -0
- package/server/public/icons/Icon-512.png +0 -0
- package/server/public/icons/Icon-maskable-192-light.png +0 -0
- package/server/public/icons/Icon-maskable-192.png +0 -0
- package/server/public/icons/Icon-maskable-512-light.png +0 -0
- package/server/public/icons/Icon-maskable-512.png +0 -0
- package/server/public/main.dart.js +88944 -85720
- package/server/routes/admin.js +632 -0
- package/server/routes/agent_profiles.js +3 -0
- package/server/routes/agents.js +1 -1
- package/server/routes/auth.js +3 -1
- package/server/routes/browser.js +14 -0
- package/server/routes/browser_extension.js +21 -4
- package/server/routes/desktop.js +10 -0
- package/server/routes/mcp.js +29 -13
- package/server/routes/memory.js +23 -1
- package/server/routes/messaging.js +2 -0
- package/server/routes/screenHistory.js +14 -8
- package/server/routes/settings.js +14 -2
- package/server/routes/stream.js +12 -3
- package/server/routes/triggers.js +4 -4
- package/server/routes/voice_assistant.js +36 -1
- package/server/routes/workspace.js +86 -0
- package/server/services/account/admin_two_factor.js +132 -0
- package/server/services/account/password_policy.js +6 -1
- package/server/services/ai/completion.js +44 -0
- package/server/services/ai/engine.js +321 -500
- package/server/services/ai/imageAnalysis.js +9 -5
- package/server/services/ai/logFormat.js +46 -0
- package/server/services/ai/loopPolicy.js +11 -0
- package/server/services/ai/messagingFallback.js +228 -0
- package/server/services/ai/models.js +194 -55
- package/server/services/ai/providerRetry.js +169 -0
- package/server/services/ai/providers/anthropic.js +15 -4
- package/server/services/ai/providers/google.js +38 -11
- package/server/services/ai/providers/grok.js +19 -68
- package/server/services/ai/providers/grokOauth.js +141 -0
- package/server/services/ai/providers/nvidia.js +154 -0
- package/server/services/ai/providers/ollama.js +76 -39
- package/server/services/ai/providers/openai.js +20 -31
- package/server/services/ai/providers/openaiCodex.js +6 -2
- package/server/services/ai/providers/openaiCompatible.js +70 -0
- package/server/services/ai/providers/openrouter.js +162 -0
- package/server/services/ai/settings.js +30 -0
- package/server/services/ai/systemPrompt.js +51 -29
- package/server/services/ai/taskAnalysis.js +60 -1
- package/server/services/ai/toolEvidence.js +207 -0
- package/server/services/ai/toolSelector.js +8 -1
- package/server/services/ai/tools.js +80 -15
- package/server/services/android/controller.js +36 -1
- package/server/services/browser/controller.js +18 -0
- package/server/services/browser/extension/manifest.js +33 -0
- package/server/services/browser/extension/provider.js +12 -6
- package/server/services/browser/extension/registry.js +229 -18
- package/server/services/desktop/protocol.js +1 -0
- package/server/services/desktop/provider.js +4 -0
- package/server/services/desktop/registry.js +104 -1
- package/server/services/desktop/screenRecorder.js +208 -98
- package/server/services/desktop/screen_recorder_support.js +46 -0
- package/server/services/integrations/google/provider.js +13 -0
- package/server/services/integrations/home_assistant/constants.js +88 -0
- package/server/services/integrations/home_assistant/network.js +207 -0
- package/server/services/integrations/home_assistant/provider.js +249 -0
- package/server/services/integrations/home_assistant/snapshot.js +98 -0
- package/server/services/integrations/home_assistant/tools.js +101 -0
- package/server/services/integrations/manager.js +5 -0
- package/server/services/integrations/registry.js +2 -0
- package/server/services/integrations/trello/provider.js +8 -2
- package/server/services/manager.js +51 -47
- package/server/services/mcp/client.js +208 -268
- package/server/services/mcp/client_support.js +172 -0
- package/server/services/mcp/recovery.js +116 -0
- package/server/services/mcp/tool_operations.js +123 -0
- package/server/services/memory/ingestion.js +185 -370
- package/server/services/memory/ingestion_coverage.js +129 -0
- package/server/services/memory/ingestion_documents.js +123 -0
- package/server/services/memory/ingestion_support.js +171 -0
- package/server/services/memory/intelligence.js +181 -0
- package/server/services/memory/manager.js +476 -32
- package/server/services/messaging/automation.js +71 -134
- package/server/services/messaging/formatting_guides.js +26 -1
- package/server/services/messaging/inbound_queue.js +111 -0
- package/server/services/messaging/manager.js +25 -8
- package/server/services/messaging/typing_keepalive.js +110 -0
- package/server/services/runtime/manager.js +7 -3
- package/server/services/runtime/settings.js +17 -0
- package/server/services/streaming/android-stream.js +296 -32
- package/server/services/streaming/stream-hub.js +1 -1
- package/server/services/tasks/integration_runtime.js +4 -1
- package/server/services/tasks/runtime.js +415 -56
- package/server/services/tasks/task_repository.js +48 -6
- package/server/services/websocket.js +8 -2
- package/server/services/widgets/service.js +17 -1
- package/server/services/workspace/manager.js +116 -0
- package/server/utils/logger.js +44 -6
- package/server/utils/security.js +3 -0
- package/server/utils/version.js +29 -19
- package/server/services/memory/openhuman_uplift.test.js +0 -98
|
@@ -13,36 +13,125 @@ const { TaskRepository } = require('./task_repository');
|
|
|
13
13
|
const { TriggerRegistry } = require('./trigger_registry');
|
|
14
14
|
const scheduleAdapter = require('./adapters/schedule');
|
|
15
15
|
const { normalizeJsonObject } = require('./utils');
|
|
16
|
+
const { normalizeOutgoingMessageForPlatform } = require('../messaging/formatting_guides');
|
|
16
17
|
|
|
17
18
|
const MAX_AUTONOMOUS_RETRIES = 1;
|
|
18
19
|
const MAX_RECURRING_TASK_START_DELAY_MS = 90 * 1000;
|
|
19
20
|
const INTEGRATION_TRIGGER_POLL_CRON = '* * * * *';
|
|
20
21
|
|
|
22
|
+
function normalizeStoredString(value) {
|
|
23
|
+
if (value == null) return '';
|
|
24
|
+
if (typeof value !== 'string') return String(value || '').trim();
|
|
25
|
+
let current = value.trim();
|
|
26
|
+
for (let i = 0; i < 2; i += 1) {
|
|
27
|
+
if (!current) return '';
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(current);
|
|
30
|
+
if (typeof parsed === 'string') {
|
|
31
|
+
current = parsed.trim();
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
return '';
|
|
35
|
+
} catch {
|
|
36
|
+
return current;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return current;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizeNotifyTarget(target = {}) {
|
|
43
|
+
const platform = normalizeStoredString(target.platform);
|
|
44
|
+
const to = normalizeStoredString(target.to);
|
|
45
|
+
if (!platform || !to) return null;
|
|
46
|
+
return { platform, to };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function stringifyTaskResult(result) {
|
|
50
|
+
if (typeof result === 'string') return result;
|
|
51
|
+
if (result == null) return '';
|
|
52
|
+
if (typeof result !== 'object') return String(result);
|
|
53
|
+
|
|
54
|
+
for (const key of ['content', 'message', 'text', 'summary', 'finalResponse', 'final_response']) {
|
|
55
|
+
if (typeof result[key] === 'string' && result[key].trim()) {
|
|
56
|
+
return result[key];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (result.result != null && result.result !== result) {
|
|
61
|
+
const nested = stringifyTaskResult(result.result);
|
|
62
|
+
if (nested) return nested;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
|
|
21
68
|
class TaskRuntime {
|
|
22
|
-
constructor(io, agentEngine, app = null) {
|
|
69
|
+
constructor(io, agentEngine, app = null, options = {}) {
|
|
23
70
|
this.io = io;
|
|
24
71
|
this.agentEngine = agentEngine;
|
|
25
72
|
this.app = app;
|
|
73
|
+
this.cron = options.cron || cron;
|
|
26
74
|
this.taskRepository = new TaskRepository();
|
|
27
75
|
this.scheduleJobs = new Map();
|
|
28
76
|
this.runningTaskExecutions = new Set();
|
|
77
|
+
this.activeExecutionPromises = new Set();
|
|
78
|
+
this.activePolls = new Map();
|
|
29
79
|
this.integrationEventCleanups = [];
|
|
30
80
|
this.triggerRegistry = new TriggerRegistry(taskAdapters);
|
|
81
|
+
this.started = false;
|
|
82
|
+
this.stopping = false;
|
|
83
|
+
this.stopPromise = null;
|
|
84
|
+
this.state = 'idle';
|
|
85
|
+
this.lastError = null;
|
|
31
86
|
}
|
|
32
87
|
|
|
33
88
|
get integrationManager() {
|
|
34
89
|
return this.app?.locals?.integrationManager || null;
|
|
35
90
|
}
|
|
36
91
|
|
|
92
|
+
getStatus() {
|
|
93
|
+
return {
|
|
94
|
+
state: this.state,
|
|
95
|
+
started: this.started,
|
|
96
|
+
stopping: this.stopping,
|
|
97
|
+
scheduledJobCount: this.scheduleJobs.size,
|
|
98
|
+
activeExecutionCount: this.activeExecutionPromises.size,
|
|
99
|
+
activePolls: Array.from(this.activePolls.keys()),
|
|
100
|
+
lastError: this.lastError,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
37
104
|
start() {
|
|
38
|
-
this.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
105
|
+
if (this.started) {
|
|
106
|
+
return this.getStatus();
|
|
107
|
+
}
|
|
108
|
+
if (this.stopPromise) {
|
|
109
|
+
throw new Error('Task runtime cannot start while shutdown is in progress.');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.started = true;
|
|
113
|
+
this.stopping = false;
|
|
114
|
+
this.state = 'starting';
|
|
115
|
+
this.lastError = null;
|
|
116
|
+
try {
|
|
117
|
+
this._loadFromDB();
|
|
118
|
+
this._startOneTimePoller();
|
|
119
|
+
this._startIntegrationPoller();
|
|
120
|
+
this.integrationEventCleanups = attachIntegrationEventSources(this);
|
|
121
|
+
this.state = 'running';
|
|
122
|
+
console.log('[Tasks] Started');
|
|
123
|
+
return this.getStatus();
|
|
124
|
+
} catch (error) {
|
|
125
|
+
this.lastError = error.message;
|
|
126
|
+
this.started = false;
|
|
127
|
+
this.stopping = false;
|
|
128
|
+
this.state = 'error';
|
|
129
|
+
this._stopScheduling();
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
43
132
|
}
|
|
44
133
|
|
|
45
|
-
|
|
134
|
+
_stopScheduling() {
|
|
46
135
|
for (const [, job] of this.scheduleJobs) {
|
|
47
136
|
job.task.stop();
|
|
48
137
|
}
|
|
@@ -51,12 +140,51 @@ class TaskRuntime {
|
|
|
51
140
|
if (poller) poller.stop();
|
|
52
141
|
}
|
|
53
142
|
for (const cleanup of this.integrationEventCleanups) {
|
|
54
|
-
|
|
143
|
+
try {
|
|
144
|
+
cleanup();
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error('[Tasks] Event source cleanup failed:', error.message);
|
|
147
|
+
}
|
|
55
148
|
}
|
|
56
149
|
this.integrationEventCleanups = [];
|
|
57
150
|
this.oneTimePoller = null;
|
|
58
151
|
this.integrationPoller = null;
|
|
59
|
-
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async stop() {
|
|
155
|
+
if (this.stopPromise) {
|
|
156
|
+
return this.stopPromise;
|
|
157
|
+
}
|
|
158
|
+
if (
|
|
159
|
+
!this.started
|
|
160
|
+
&& this.state === 'stopped'
|
|
161
|
+
&& this.scheduleJobs.size === 0
|
|
162
|
+
&& this.activePolls.size === 0
|
|
163
|
+
&& this.activeExecutionPromises.size === 0
|
|
164
|
+
) {
|
|
165
|
+
return this.getStatus();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
this.started = false;
|
|
169
|
+
this.stopping = true;
|
|
170
|
+
this.state = 'stopping';
|
|
171
|
+
this._stopScheduling();
|
|
172
|
+
|
|
173
|
+
this.stopPromise = (async () => {
|
|
174
|
+
while (this.activePolls.size > 0 || this.activeExecutionPromises.size > 0) {
|
|
175
|
+
await Promise.allSettled([
|
|
176
|
+
...this.activePolls.values(),
|
|
177
|
+
...this.activeExecutionPromises,
|
|
178
|
+
]);
|
|
179
|
+
}
|
|
180
|
+
this.stopping = false;
|
|
181
|
+
this.state = 'stopped';
|
|
182
|
+
console.log('[Tasks] Stopped');
|
|
183
|
+
return this.getStatus();
|
|
184
|
+
})().finally(() => {
|
|
185
|
+
this.stopPromise = null;
|
|
186
|
+
});
|
|
187
|
+
return this.stopPromise;
|
|
60
188
|
}
|
|
61
189
|
|
|
62
190
|
getTriggerCatalog(userId, options = {}) {
|
|
@@ -138,6 +266,8 @@ class TaskRuntime {
|
|
|
138
266
|
manual: true,
|
|
139
267
|
triggerType: task.trigger_type || 'schedule',
|
|
140
268
|
triggerSource: 'manual',
|
|
269
|
+
}).catch((error) => {
|
|
270
|
+
console.error(`[Tasks] Manual task ${taskId} error:`, error.message);
|
|
141
271
|
});
|
|
142
272
|
return { running: true };
|
|
143
273
|
}
|
|
@@ -152,9 +282,8 @@ class TaskRuntime {
|
|
|
152
282
|
if (String(task.last_trigger_fingerprint || '') === fingerprint) {
|
|
153
283
|
return { skipped: true, reason: 'duplicate_trigger' };
|
|
154
284
|
}
|
|
155
|
-
this.taskRepository.markTaskTriggered(taskId, userId, fingerprint);
|
|
156
285
|
|
|
157
|
-
|
|
286
|
+
const result = await this._executeTask(taskId, userId, {
|
|
158
287
|
manual: false,
|
|
159
288
|
oneTime: false,
|
|
160
289
|
scheduledAt: triggerPayload.timestamp || new Date().toISOString(),
|
|
@@ -162,58 +291,106 @@ class TaskRuntime {
|
|
|
162
291
|
triggerSource: task.trigger_type || 'schedule',
|
|
163
292
|
triggerPayload: triggerPayload.context || {},
|
|
164
293
|
});
|
|
294
|
+
if (!result?.error && !result?.skipped) {
|
|
295
|
+
this.taskRepository.markTaskTriggered(taskId, userId, fingerprint);
|
|
296
|
+
}
|
|
297
|
+
return result;
|
|
165
298
|
}
|
|
166
299
|
|
|
167
300
|
_startOneTimePoller() {
|
|
168
|
-
this.oneTimePoller = cron.schedule('* * * * *',
|
|
169
|
-
|
|
301
|
+
this.oneTimePoller = this.cron.schedule('* * * * *', () => {
|
|
302
|
+
return this._runPoll('one_time', () => this._runDueOneTimeTasks(), (error) => {
|
|
303
|
+
console.error('[Tasks] One-time task poll failed:', error.message);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
}
|
|
170
307
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
308
|
+
async _runDueOneTimeTasks() {
|
|
309
|
+
const due = this.taskRepository.listDueOneTimeTasks();
|
|
310
|
+
|
|
311
|
+
for (const task of due) {
|
|
312
|
+
this.scheduleJobs.delete(task.id);
|
|
313
|
+
try {
|
|
314
|
+
const result = await this._executeTask(task.id, task.user_id, {
|
|
315
|
+
scheduledAt: task.run_at || new Date().toISOString(),
|
|
316
|
+
oneTime: true,
|
|
317
|
+
triggerType: 'schedule',
|
|
318
|
+
triggerSource: 'schedule',
|
|
319
|
+
});
|
|
320
|
+
if (result?.skipped) {
|
|
321
|
+
continue;
|
|
182
322
|
}
|
|
183
|
-
this.taskRepository.
|
|
323
|
+
this.taskRepository.deleteTask(task.id, task.user_id);
|
|
184
324
|
this.io.to(`user:${task.user_id}`).emit('tasks:task_deleted', { taskId: task.id });
|
|
325
|
+
} catch (err) {
|
|
326
|
+
console.error(`[Tasks] One-time task ${task.id} error:`, err.message);
|
|
185
327
|
}
|
|
186
|
-
}
|
|
328
|
+
}
|
|
187
329
|
}
|
|
188
330
|
|
|
189
331
|
_startIntegrationPoller() {
|
|
190
|
-
this.integrationPoller = cron.schedule(INTEGRATION_TRIGGER_POLL_CRON,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
332
|
+
this.integrationPoller = this.cron.schedule(INTEGRATION_TRIGGER_POLL_CRON, () => {
|
|
333
|
+
return this._runPoll('integration', async () => {
|
|
334
|
+
const tasks = this.taskRepository.listEnabledByTriggerTypes(POLLED_TRIGGER_TYPES);
|
|
335
|
+
for (const task of tasks) {
|
|
336
|
+
try {
|
|
337
|
+
await pollIntegrationTask(this, task);
|
|
338
|
+
} catch (error) {
|
|
339
|
+
console.error(`[Tasks] Trigger poll failed for task ${task.id}:`, error.message);
|
|
340
|
+
}
|
|
197
341
|
}
|
|
198
|
-
}
|
|
342
|
+
}, (error) => {
|
|
343
|
+
console.error('[Tasks] Integration trigger poll failed:', error.message);
|
|
344
|
+
});
|
|
199
345
|
});
|
|
200
346
|
}
|
|
201
347
|
|
|
348
|
+
_runPoll(name, callback, onError) {
|
|
349
|
+
const active = this.activePolls.get(name);
|
|
350
|
+
if (active) {
|
|
351
|
+
return active;
|
|
352
|
+
}
|
|
353
|
+
if (this.stopping) {
|
|
354
|
+
return Promise.resolve({ skipped: true, reason: 'runtime_stopping' });
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const promise = Promise.resolve()
|
|
358
|
+
.then(callback)
|
|
359
|
+
.catch((error) => {
|
|
360
|
+
this.lastError = error.message;
|
|
361
|
+
onError(error);
|
|
362
|
+
return { error: error.message };
|
|
363
|
+
})
|
|
364
|
+
.finally(() => {
|
|
365
|
+
if (this.activePolls.get(name) === promise) {
|
|
366
|
+
this.activePolls.delete(name);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
this.activePolls.set(name, promise);
|
|
370
|
+
return promise;
|
|
371
|
+
}
|
|
372
|
+
|
|
202
373
|
async _registerTask(task) {
|
|
203
|
-
if (!task
|
|
374
|
+
if (!task) return;
|
|
375
|
+
this._unregisterTask(task.id);
|
|
376
|
+
if (!task.enabled) return;
|
|
204
377
|
if ((task.trigger_type || 'schedule') !== 'schedule') return;
|
|
205
378
|
const triggerConfig = this._normalizeJson(task.trigger_config);
|
|
206
379
|
if (triggerConfig.mode === 'one_time') return;
|
|
207
380
|
const cronExpression = String(triggerConfig.cronExpression || '').trim();
|
|
208
381
|
if (!cronExpression) return;
|
|
209
|
-
const job = cron.schedule(cronExpression, async () => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
382
|
+
const job = this.cron.schedule(cronExpression, async () => {
|
|
383
|
+
try {
|
|
384
|
+
await this._executeTask(task.id, task.user_id, {
|
|
385
|
+
scheduledAt: new Date().toISOString(),
|
|
386
|
+
manual: false,
|
|
387
|
+
oneTime: false,
|
|
388
|
+
triggerType: 'schedule',
|
|
389
|
+
triggerSource: 'schedule',
|
|
390
|
+
});
|
|
391
|
+
} catch (error) {
|
|
392
|
+
console.error(`[Tasks] Scheduled task ${task.id} error:`, error.message);
|
|
393
|
+
}
|
|
217
394
|
});
|
|
218
395
|
this.scheduleJobs.set(task.id, { task: job, userId: task.user_id });
|
|
219
396
|
}
|
|
@@ -227,6 +404,9 @@ class TaskRuntime {
|
|
|
227
404
|
}
|
|
228
405
|
|
|
229
406
|
async _executeTask(taskId, userId, executionMeta = {}) {
|
|
407
|
+
if (this.stopping) {
|
|
408
|
+
return { skipped: true, reason: 'runtime_stopping' };
|
|
409
|
+
}
|
|
230
410
|
const executionKey = `${userId}:${taskId}`;
|
|
231
411
|
if (this.runningTaskExecutions.has(executionKey)) {
|
|
232
412
|
this.io.to(`user:${userId}`).emit('tasks:task_skipped', {
|
|
@@ -238,10 +418,13 @@ class TaskRuntime {
|
|
|
238
418
|
}
|
|
239
419
|
|
|
240
420
|
this.runningTaskExecutions.add(executionKey);
|
|
421
|
+
const executionPromise = this._executeTaskSerial(taskId, userId, executionMeta);
|
|
422
|
+
this.activeExecutionPromises.add(executionPromise);
|
|
241
423
|
try {
|
|
242
|
-
return await
|
|
424
|
+
return await executionPromise;
|
|
243
425
|
} finally {
|
|
244
426
|
this.runningTaskExecutions.delete(executionKey);
|
|
427
|
+
this.activeExecutionPromises.delete(executionPromise);
|
|
245
428
|
}
|
|
246
429
|
}
|
|
247
430
|
|
|
@@ -273,6 +456,15 @@ class TaskRuntime {
|
|
|
273
456
|
this.taskRepository.markTaskRun(taskId, userId);
|
|
274
457
|
this.io.to(`user:${userId}`).emit('tasks:task_running', { taskId, timestamp: new Date().toISOString() });
|
|
275
458
|
|
|
459
|
+
let normalizedConfig = taskConfig;
|
|
460
|
+
const taskName = task.name || `Task ${taskId}`;
|
|
461
|
+
const deliveryState = {
|
|
462
|
+
messagingSent: false,
|
|
463
|
+
noResponse: false,
|
|
464
|
+
lastSentMessage: '',
|
|
465
|
+
sentMessages: [],
|
|
466
|
+
};
|
|
467
|
+
let completedRunId = null;
|
|
276
468
|
try {
|
|
277
469
|
if (task.task_type === 'widget_refresh') {
|
|
278
470
|
const widgetService = this.app?.locals?.widgetService;
|
|
@@ -288,8 +480,7 @@ class TaskRuntime {
|
|
|
288
480
|
return result;
|
|
289
481
|
}
|
|
290
482
|
|
|
291
|
-
|
|
292
|
-
const taskName = task.name || `Task ${taskId}`;
|
|
483
|
+
normalizedConfig = this._ensureDefaultNotifyTarget(userId, agentId, taskConfig, taskId);
|
|
293
484
|
const triggerSummary = this._summarizeTrigger(task.trigger_type, triggerConfig);
|
|
294
485
|
let notifyHint = '';
|
|
295
486
|
|
|
@@ -315,7 +506,6 @@ class TaskRuntime {
|
|
|
315
506
|
].filter(Boolean).join('\n\n');
|
|
316
507
|
|
|
317
508
|
const conversationId = this._getTaskConversation(userId, taskId, taskName, agentId);
|
|
318
|
-
const deliveryState = { messagingSent: false, lastSentMessage: '', sentMessages: [] };
|
|
319
509
|
let attempt = 0;
|
|
320
510
|
let recoveryNote = '';
|
|
321
511
|
while (attempt <= MAX_AUTONOMOUS_RETRIES) {
|
|
@@ -330,6 +520,7 @@ class TaskRuntime {
|
|
|
330
520
|
deliveryState,
|
|
331
521
|
allowMultipleProactiveMessages: normalizedConfig.allowMultipleMessages === true || normalizedConfig.allow_multiple_messages === true,
|
|
332
522
|
skipTaskAnalysis: true,
|
|
523
|
+
skipDeliverableWorkflow: true,
|
|
333
524
|
skipGlobalRecall: true,
|
|
334
525
|
skipConversationHistory: true,
|
|
335
526
|
skipConversationMaintenance: true,
|
|
@@ -342,11 +533,42 @@ class TaskRuntime {
|
|
|
342
533
|
const result = typeof this.agentEngine.runWithModel === 'function'
|
|
343
534
|
? await this.agentEngine.runWithModel(userId, finalPrompt, runOptions, normalizedConfig.model || null)
|
|
344
535
|
: await this.agentEngine.run(userId, finalPrompt, runOptions);
|
|
536
|
+
completedRunId = result?.runId || null;
|
|
537
|
+
const fallbackDelivery = await this._deliverTaskResultIfNeeded({
|
|
538
|
+
userId,
|
|
539
|
+
agentId,
|
|
540
|
+
taskId,
|
|
541
|
+
taskConfig: normalizedConfig,
|
|
542
|
+
result,
|
|
543
|
+
deliveryState,
|
|
544
|
+
});
|
|
545
|
+
if (fallbackDelivery && result && typeof result === 'object') {
|
|
546
|
+
result.taskDelivery = fallbackDelivery;
|
|
547
|
+
}
|
|
548
|
+
if (fallbackDelivery?.error) {
|
|
549
|
+
const deliveryError = new Error(fallbackDelivery.error);
|
|
550
|
+
deliveryError.code = 'TASK_DELIVERY_FAILED';
|
|
551
|
+
throw deliveryError;
|
|
552
|
+
}
|
|
553
|
+
if (
|
|
554
|
+
!deliveryState.messagingSent
|
|
555
|
+
&& !deliveryState.noResponse
|
|
556
|
+
&& !stringifyTaskResult(result).trim()
|
|
557
|
+
) {
|
|
558
|
+
throw new Error(
|
|
559
|
+
'Background task completed without producing a result or an explicit no-response decision.',
|
|
560
|
+
);
|
|
561
|
+
}
|
|
345
562
|
this.io.to(`user:${userId}`).emit('tasks:task_complete', { taskId, result });
|
|
346
563
|
return result;
|
|
347
564
|
} catch (err) {
|
|
565
|
+
if (completedRunId) {
|
|
566
|
+
this.taskRepository.markAgentRunFailed(completedRunId, userId, err.message);
|
|
567
|
+
}
|
|
568
|
+
if (err?.code === 'TASK_DELIVERY_FAILED') throw err;
|
|
348
569
|
if (attempt >= MAX_AUTONOMOUS_RETRIES) throw err;
|
|
349
570
|
attempt += 1;
|
|
571
|
+
completedRunId = null;
|
|
350
572
|
recoveryNote = [
|
|
351
573
|
'\n\n[SYSTEM: Previous task attempt failed]',
|
|
352
574
|
`Error: ${String(err?.message || 'Unknown runtime error')}`,
|
|
@@ -361,8 +583,24 @@ class TaskRuntime {
|
|
|
361
583
|
}
|
|
362
584
|
} catch (err) {
|
|
363
585
|
console.error(`[Tasks] Task ${taskId} error:`, err.message);
|
|
364
|
-
|
|
365
|
-
|
|
586
|
+
if (err?.code !== 'TASK_DELIVERY_FAILED') {
|
|
587
|
+
await this._deliverTaskResultIfNeeded({
|
|
588
|
+
userId,
|
|
589
|
+
agentId,
|
|
590
|
+
taskId,
|
|
591
|
+
taskConfig: normalizedConfig,
|
|
592
|
+
result: {
|
|
593
|
+
content: `Background task "${taskName}" could not complete after retrying. Check the task run logs for details.`,
|
|
594
|
+
},
|
|
595
|
+
deliveryState,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
this.io.to(`user:${userId}`).emit('tasks:task_skipped', {
|
|
599
|
+
taskId,
|
|
600
|
+
reason: 'execution_failed',
|
|
601
|
+
timestamp: new Date().toISOString(),
|
|
602
|
+
});
|
|
603
|
+
return { skipped: false, error: err.message, runId: completedRunId };
|
|
366
604
|
}
|
|
367
605
|
}
|
|
368
606
|
|
|
@@ -451,7 +689,10 @@ class TaskRuntime {
|
|
|
451
689
|
triggerSummary,
|
|
452
690
|
nextRun: triggerType === 'schedule' ? scheduleAdapter.nextRun(triggerConfig) : null,
|
|
453
691
|
enabled: !!row.enabled,
|
|
454
|
-
lastRun: row.last_run || null,
|
|
692
|
+
lastRun: row.last_run_started_at || row.last_run || null,
|
|
693
|
+
lastRunId: row.last_run_id || null,
|
|
694
|
+
lastRunStatus: row.last_run_status || null,
|
|
695
|
+
lastRunError: row.last_run_error || null,
|
|
455
696
|
lastTriggeredAt: row.last_triggered_at || null,
|
|
456
697
|
taskType: row.task_type || 'agent_prompt',
|
|
457
698
|
taskConfig,
|
|
@@ -485,25 +726,143 @@ class TaskRuntime {
|
|
|
485
726
|
|
|
486
727
|
_getDefaultNotifyTarget(userId, agentId = null) {
|
|
487
728
|
const scopedAgentId = resolveAgentId(userId, agentId);
|
|
488
|
-
return {
|
|
729
|
+
return normalizeNotifyTarget({
|
|
489
730
|
platform: this._getAgentSetting(userId, scopedAgentId, 'last_platform'),
|
|
490
731
|
to: this._getAgentSetting(userId, scopedAgentId, 'last_chat_id'),
|
|
491
|
-
};
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
_buildNotifyTargets(userId, agentId, taskConfig = {}) {
|
|
736
|
+
const scopedAgentId = resolveAgentId(userId, agentId);
|
|
737
|
+
const candidates = [
|
|
738
|
+
normalizeNotifyTarget({
|
|
739
|
+
platform: taskConfig.notifyPlatform,
|
|
740
|
+
to: taskConfig.notifyTo,
|
|
741
|
+
}),
|
|
742
|
+
this._getDefaultNotifyTarget(userId, scopedAgentId),
|
|
743
|
+
...this.taskRepository.listRecentMessageTargets(userId, scopedAgentId).map((row) => normalizeNotifyTarget({
|
|
744
|
+
platform: row.platform,
|
|
745
|
+
to: row.platform_chat_id,
|
|
746
|
+
})),
|
|
747
|
+
];
|
|
748
|
+
|
|
749
|
+
const unique = [];
|
|
750
|
+
const seen = new Set();
|
|
751
|
+
for (const target of candidates) {
|
|
752
|
+
if (!target) continue;
|
|
753
|
+
const key = `${target.platform}:${target.to}`;
|
|
754
|
+
if (seen.has(key)) continue;
|
|
755
|
+
seen.add(key);
|
|
756
|
+
unique.push(target);
|
|
757
|
+
}
|
|
758
|
+
return unique;
|
|
492
759
|
}
|
|
493
760
|
|
|
494
761
|
_ensureDefaultNotifyTarget(userId, agentId, taskConfig, taskId) {
|
|
495
762
|
const normalized = { ...taskConfig };
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
763
|
+
const existingTarget = normalizeNotifyTarget({
|
|
764
|
+
platform: normalized.notifyPlatform,
|
|
765
|
+
to: normalized.notifyTo,
|
|
766
|
+
});
|
|
767
|
+
if (existingTarget) {
|
|
768
|
+
normalized.notifyPlatform = existingTarget.platform;
|
|
769
|
+
normalized.notifyTo = existingTarget.to;
|
|
770
|
+
}
|
|
771
|
+
if (!normalized.callTo && !existingTarget) {
|
|
772
|
+
const notifyTarget = this._buildNotifyTargets(userId, agentId, normalized)[0];
|
|
773
|
+
if (notifyTarget) {
|
|
499
774
|
normalized.notifyPlatform = notifyTarget.platform;
|
|
500
775
|
normalized.notifyTo = notifyTarget.to;
|
|
501
|
-
this.taskRepository.updateTaskConfig(taskId, userId, normalized);
|
|
502
776
|
}
|
|
503
777
|
}
|
|
778
|
+
if (
|
|
779
|
+
normalized.notifyPlatform !== taskConfig.notifyPlatform
|
|
780
|
+
|| normalized.notifyTo !== taskConfig.notifyTo
|
|
781
|
+
) {
|
|
782
|
+
this.taskRepository.updateTaskConfig(taskId, userId, normalized);
|
|
783
|
+
}
|
|
504
784
|
return normalized;
|
|
505
785
|
}
|
|
506
786
|
|
|
787
|
+
async _deliverTaskResultIfNeeded({
|
|
788
|
+
userId,
|
|
789
|
+
agentId,
|
|
790
|
+
taskId,
|
|
791
|
+
taskConfig,
|
|
792
|
+
result,
|
|
793
|
+
deliveryState,
|
|
794
|
+
}) {
|
|
795
|
+
if (deliveryState?.messagingSent || deliveryState?.noResponse || taskConfig.callTo) return null;
|
|
796
|
+
const targets = this._buildNotifyTargets(userId, agentId, taskConfig);
|
|
797
|
+
if (!targets.length) return null;
|
|
798
|
+
|
|
799
|
+
const manager = this.app?.locals?.messagingManager || this.agentEngine?.messagingManager || null;
|
|
800
|
+
if (!manager) {
|
|
801
|
+
return {
|
|
802
|
+
sent: false,
|
|
803
|
+
error: 'Messaging delivery is unavailable on this server.',
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
let lastError = null;
|
|
808
|
+
for (const target of targets) {
|
|
809
|
+
const message = normalizeOutgoingMessageForPlatform(
|
|
810
|
+
target.platform,
|
|
811
|
+
stringifyTaskResult(result),
|
|
812
|
+
{ stripNoResponseMarker: false },
|
|
813
|
+
);
|
|
814
|
+
if (!message || message.toUpperCase() === '[NO RESPONSE]') return null;
|
|
815
|
+
|
|
816
|
+
const status = typeof manager.getPlatformStatus === 'function'
|
|
817
|
+
? manager.getPlatformStatus(userId, target.platform, { agentId })
|
|
818
|
+
: null;
|
|
819
|
+
if (!status || status.status !== 'connected') {
|
|
820
|
+
lastError = new Error(`Platform ${target.platform} is not connected on this server.`);
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
try {
|
|
825
|
+
const sendResult = await manager.sendMessage(userId, target.platform, target.to, message, {
|
|
826
|
+
agentId,
|
|
827
|
+
runId: result?.runId || null,
|
|
828
|
+
persistConversation: true,
|
|
829
|
+
});
|
|
830
|
+
deliveryState.messagingSent = true;
|
|
831
|
+
deliveryState.lastSentMessage = message;
|
|
832
|
+
if (!Array.isArray(deliveryState.sentMessages)) {
|
|
833
|
+
deliveryState.sentMessages = [];
|
|
834
|
+
}
|
|
835
|
+
deliveryState.sentMessages.push(message);
|
|
836
|
+
|
|
837
|
+
if (taskConfig.notifyPlatform !== target.platform || taskConfig.notifyTo !== target.to) {
|
|
838
|
+
this.taskRepository.updateTaskConfig(taskId, userId, {
|
|
839
|
+
...taskConfig,
|
|
840
|
+
notifyPlatform: target.platform,
|
|
841
|
+
notifyTo: target.to,
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return {
|
|
846
|
+
sent: true,
|
|
847
|
+
platform: target.platform,
|
|
848
|
+
to: target.to,
|
|
849
|
+
result: sendResult,
|
|
850
|
+
};
|
|
851
|
+
} catch (error) {
|
|
852
|
+
lastError = error;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (lastError) {
|
|
857
|
+
console.error(`[Tasks] Task ${taskId} notification delivery failed:`, lastError.message);
|
|
858
|
+
return {
|
|
859
|
+
sent: false,
|
|
860
|
+
error: lastError.message,
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
|
|
507
866
|
_getTaskConversation(userId, taskId, taskName, agentId = null) {
|
|
508
867
|
const scopedAgentId = resolveAgentId(userId, agentId);
|
|
509
868
|
const platform = 'tasks';
|