neoagent 2.2.1-beta.6 → 2.2.1-beta.7
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/docs/automation.md +2 -2
- package/docs/capabilities.md +7 -10
- package/docs/hardware.md +4 -7
- package/docs/index.md +6 -7
- package/docs/integrations.md +1 -1
- package/docs/operations.md +1 -1
- package/docs/why-neoagent.md +2 -2
- package/package.json +1 -1
- package/server/db/database.js +76 -61
- package/server/http/routes.js +1 -2
- package/server/public/assets/AssetManifest.json +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 +65118 -64805
- package/server/routes/{scheduler.js → tasks.js} +31 -29
- package/server/routes/widgets.js +7 -7
- package/server/services/ai/capabilityHealth.js +4 -4
- package/server/services/ai/engine.js +9 -9
- package/server/services/ai/systemPrompt.js +7 -7
- package/server/services/ai/taskAnalysis.js +3 -3
- package/server/services/ai/toolResult.js +6 -8
- package/server/services/ai/tools.js +62 -95
- package/server/services/commands/router.js +14 -6
- package/server/services/integrations/whatsapp/provider.js +23 -1
- package/server/services/manager.js +14 -14
- package/server/services/memory/manager.js +7 -7
- package/server/services/memory/policy.js +1 -1
- package/server/services/messaging/formatting_guides.js +0 -4
- package/server/services/messaging/manager.js +0 -2
- package/server/services/tasks/adapters/gmail_message_received.js +36 -0
- package/server/services/tasks/adapters/index.js +10 -0
- package/server/services/tasks/adapters/outlook_email_received.js +38 -0
- package/server/services/tasks/adapters/schedule.js +57 -0
- package/server/services/tasks/adapters/slack_message_received.js +39 -0
- package/server/services/tasks/adapters/teams_message_received.js +39 -0
- package/server/services/tasks/adapters/whatsapp_personal_message_received.js +42 -0
- package/server/services/tasks/integration_runtime.js +260 -0
- package/server/services/tasks/runtime.js +539 -0
- package/server/services/{scheduler/cron_utils.js → tasks/schedule_utils.js} +2 -0
- package/server/services/tasks/security.js +60 -0
- package/server/services/tasks/task_repository.js +162 -0
- package/server/services/tasks/trigger_registry.js +29 -0
- package/server/services/tasks/utils.js +45 -0
- package/server/services/websocket.js +1 -1
- package/server/services/widgets/service.js +37 -25
- package/server/routes/wearable_device.js +0 -147
- package/server/services/messaging/waveshare_wearable.js +0 -40
- package/server/services/scheduler/cron.js +0 -580
- package/server/services/wearables/device_auth.js +0 -228
|
@@ -92,7 +92,7 @@ function compactToolDefinition(tool, options = {}) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
function isProactiveTrigger(triggerSource) {
|
|
95
|
-
return triggerSource === '
|
|
95
|
+
return triggerSource === 'schedule' || triggerSource === 'tasks';
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
function getRunState(engine, runId) {
|
|
@@ -559,7 +559,7 @@ function getAvailableTools(app, options = {}) {
|
|
|
559
559
|
},
|
|
560
560
|
{
|
|
561
561
|
name: 'memory_save',
|
|
562
|
-
description: 'Save ONE specific, self-contained fact to long-term semantic memory. RULES: (1) One discrete fact per call — if you have 10 facts, call this 10 times. (2) The ENTIRE value must be IN the content string itself — never write a pointer/reference like "user shared a profile" or "see chat history for details". That is useless. (3) Content must be a complete statement a stranger could read cold and understand. (4) Only save durable facts, preferences, or stable project context — never save recent
|
|
562
|
+
description: 'Save ONE specific, self-contained fact to long-term semantic memory. RULES: (1) One discrete fact per call — if you have 10 facts, call this 10 times. (2) The ENTIRE value must be IN the content string itself — never write a pointer/reference like "user shared a profile" or "see chat history for details". That is useless. (3) Content must be a complete statement a stranger could read cold and understand. (4) Only save durable facts, preferences, or stable project context — never save recent task runs, task statuses, execution receipts, or other transient operational logs. GOOD: "XYZ lives in" / "XYZ prefers dark mode". BAD: "User pasted a profile dump" / "XYZ shared lots of details — see chat history" / "XYZ gave a big list of projects" / "Recent task run: backup completed".',
|
|
563
563
|
parameters: {
|
|
564
564
|
type: 'object',
|
|
565
565
|
properties: {
|
|
@@ -870,68 +870,54 @@ function getAvailableTools(app, options = {}) {
|
|
|
870
870
|
}
|
|
871
871
|
},
|
|
872
872
|
{
|
|
873
|
-
name: '
|
|
874
|
-
description: 'Create a
|
|
873
|
+
name: 'create_task',
|
|
874
|
+
description: 'Create a background task with a named trigger and self-contained prompt.',
|
|
875
875
|
parameters: {
|
|
876
876
|
type: 'object',
|
|
877
877
|
properties: {
|
|
878
|
-
name: { type: 'string', description: 'Short descriptive name for the task' },
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
878
|
+
name: { type: 'string', description: 'Short descriptive name for the task.' },
|
|
879
|
+
trigger_type: { type: 'string', description: 'Trigger type such as schedule, gmail_message_received, outlook_email_received, slack_message_received, teams_message_received, or whatsapp_personal_message_received.' },
|
|
880
|
+
trigger_config: { type: 'object', description: 'Trigger-specific configuration object.' },
|
|
881
|
+
prompt: { type: 'string', description: 'The instructions the agent will run when the trigger fires.' },
|
|
882
|
+
enabled: { type: 'boolean', description: 'Whether to activate immediately.' },
|
|
883
|
+
model: { type: 'string', description: 'Optional model override.' },
|
|
884
|
+
call_to: { type: 'string', description: 'Optional E.164 phone number to call via Telnyx when this task fires.' },
|
|
885
|
+
call_greeting: { type: 'string', description: 'Optional spoken greeting hint for make_call.' }
|
|
885
886
|
},
|
|
886
|
-
required: ['name', '
|
|
887
|
+
required: ['name', 'trigger_type', 'trigger_config', 'prompt']
|
|
887
888
|
}
|
|
888
889
|
},
|
|
889
890
|
{
|
|
890
|
-
name: '
|
|
891
|
-
description: '
|
|
892
|
-
parameters: {
|
|
893
|
-
type: 'object',
|
|
894
|
-
properties: {
|
|
895
|
-
name: { type: 'string', description: 'Short descriptive name, e.g. "Remind about meeting"' },
|
|
896
|
-
run_at: { type: 'string', description: 'ISO 8601 datetime when the run should fire, e.g. "2026-03-09T22:00:00"' },
|
|
897
|
-
prompt: { type: 'string', description: 'The prompt/instructions the agent will execute at that time. Be specific.' },
|
|
898
|
-
model: { type: 'string', description: 'Optional specific AI model ID to force for this run. Omit to use the normal automatic/default model selection.' },
|
|
899
|
-
call_to: { type: 'string', description: 'Optional E.164 phone number to call via Telnyx when this fires.' },
|
|
900
|
-
call_greeting: { type: 'string', description: 'Opening sentence spoken when the Telnyx call is answered.' }
|
|
901
|
-
},
|
|
902
|
-
required: ['name', 'run_at', 'prompt']
|
|
903
|
-
}
|
|
904
|
-
},
|
|
905
|
-
{
|
|
906
|
-
name: 'list_scheduled_tasks',
|
|
907
|
-
description: 'List all scheduled tasks/cron jobs for this user.',
|
|
891
|
+
name: 'list_tasks',
|
|
892
|
+
description: 'List all tasks for this user and agent.',
|
|
908
893
|
parameters: { type: 'object', properties: {} }
|
|
909
894
|
},
|
|
910
895
|
{
|
|
911
|
-
name: '
|
|
912
|
-
description: 'Delete a
|
|
896
|
+
name: 'delete_task',
|
|
897
|
+
description: 'Delete a task by its ID.',
|
|
913
898
|
parameters: {
|
|
914
899
|
type: 'object',
|
|
915
900
|
properties: {
|
|
916
|
-
task_id: { type: 'number', description: 'The numeric ID of the task to delete
|
|
901
|
+
task_id: { type: 'number', description: 'The numeric ID of the task to delete.' }
|
|
917
902
|
},
|
|
918
903
|
required: ['task_id']
|
|
919
904
|
}
|
|
920
905
|
},
|
|
921
906
|
{
|
|
922
|
-
name: '
|
|
923
|
-
description: 'Update an existing
|
|
907
|
+
name: 'update_task',
|
|
908
|
+
description: 'Update an existing task, including its trigger, prompt, or enabled state.',
|
|
924
909
|
parameters: {
|
|
925
910
|
type: 'object',
|
|
926
911
|
properties: {
|
|
927
|
-
task_id: { type: 'number', description: 'The numeric ID of the task to update
|
|
928
|
-
name: { type: 'string', description: 'New name for the task' },
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
912
|
+
task_id: { type: 'number', description: 'The numeric ID of the task to update.' },
|
|
913
|
+
name: { type: 'string', description: 'New name for the task.' },
|
|
914
|
+
trigger_type: { type: 'string', description: 'Updated trigger type.' },
|
|
915
|
+
trigger_config: { type: 'object', description: 'Updated trigger-specific configuration.' },
|
|
916
|
+
prompt: { type: 'string', description: 'Updated task prompt.' },
|
|
917
|
+
enabled: { type: 'boolean', description: 'Enable or disable the task.' },
|
|
918
|
+
model: { type: 'string', description: 'Specific AI model ID for this task. Set to empty string to clear the override.' },
|
|
919
|
+
call_to: { type: 'string', description: 'Optional E.164 phone number to call via Telnyx when this task fires. Set to empty string to remove.' },
|
|
920
|
+
call_greeting: { type: 'string', description: 'Updated spoken greeting hint.' }
|
|
935
921
|
},
|
|
936
922
|
required: ['task_id']
|
|
937
923
|
}
|
|
@@ -1151,7 +1137,7 @@ function getAvailableTools(app, options = {}) {
|
|
|
1151
1137
|
tools.push(...integrationTools);
|
|
1152
1138
|
}
|
|
1153
1139
|
|
|
1154
|
-
if (options.triggerSource === '
|
|
1140
|
+
if ((options.triggerSource === 'schedule' || options.triggerSource === 'tasks') && options.widgetId) {
|
|
1155
1141
|
tools.push({
|
|
1156
1142
|
name: 'save_widget_snapshot',
|
|
1157
1143
|
description: 'Save the refreshed structured snapshot for the widget that is currently being updated. Call this exactly once per widget refresh run.',
|
|
@@ -1246,7 +1232,7 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
1246
1232
|
const mcp = () => app?.locals?.mcpManager || app?.locals?.mcpClient || engine.mcpManager;
|
|
1247
1233
|
const integrations = () => app?.locals?.integrationManager || null;
|
|
1248
1234
|
const sk = () => app?.locals?.skillRunner || engine.skillRunner;
|
|
1249
|
-
const
|
|
1235
|
+
const taskRuntime = () => app?.locals?.taskRuntime || engine.taskRuntime;
|
|
1250
1236
|
const rec = () => app?.locals?.recordingManager || null;
|
|
1251
1237
|
const widgets = () => app?.locals?.widgetService || null;
|
|
1252
1238
|
|
|
@@ -1837,7 +1823,7 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
1837
1823
|
return {
|
|
1838
1824
|
called: false,
|
|
1839
1825
|
skipped: true,
|
|
1840
|
-
reason: 'A proactive notification was already sent in this
|
|
1826
|
+
reason: 'A proactive notification was already sent in this task run; duplicate make_call was suppressed.'
|
|
1841
1827
|
};
|
|
1842
1828
|
}
|
|
1843
1829
|
|
|
@@ -1896,7 +1882,7 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
1896
1882
|
return {
|
|
1897
1883
|
sent: false,
|
|
1898
1884
|
skipped: true,
|
|
1899
|
-
reason: 'A proactive message was already sent in this
|
|
1885
|
+
reason: 'A proactive message was already sent in this task run; duplicate send_message was suppressed.'
|
|
1900
1886
|
};
|
|
1901
1887
|
}
|
|
1902
1888
|
|
|
@@ -1904,7 +1890,7 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
1904
1890
|
agentId,
|
|
1905
1891
|
mediaPath: args.media_path,
|
|
1906
1892
|
runId,
|
|
1907
|
-
persistConversation: triggerSource === '
|
|
1893
|
+
persistConversation: triggerSource === 'schedule' || triggerSource === 'tasks'
|
|
1908
1894
|
});
|
|
1909
1895
|
// Track that the agent explicitly sent a message during this run
|
|
1910
1896
|
if (!suppressReply && sendResult?.suppressed !== true) {
|
|
@@ -2104,7 +2090,7 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2104
2090
|
const message = typeof args.message === 'string' ? args.message.trim() : '';
|
|
2105
2091
|
if (!message) return { error: 'message is required' };
|
|
2106
2092
|
|
|
2107
|
-
if (triggerSource === '
|
|
2093
|
+
if (triggerSource === 'schedule' || triggerSource === 'tasks') {
|
|
2108
2094
|
const manager = msg();
|
|
2109
2095
|
if (!manager) {
|
|
2110
2096
|
throw new Error('Messaging manager not available');
|
|
@@ -2120,7 +2106,7 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2120
2106
|
return {
|
|
2121
2107
|
sent: false,
|
|
2122
2108
|
skipped: true,
|
|
2123
|
-
reason: 'A notification was already sent in this run; duplicate
|
|
2109
|
+
reason: 'A notification was already sent in this run; duplicate task message was suppressed.'
|
|
2124
2110
|
};
|
|
2125
2111
|
}
|
|
2126
2112
|
|
|
@@ -2140,7 +2126,7 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2140
2126
|
|
|
2141
2127
|
let taskConfig = null;
|
|
2142
2128
|
let taskTarget = null;
|
|
2143
|
-
if (triggerSource === '
|
|
2129
|
+
if ((triggerSource === 'schedule' || triggerSource === 'tasks') && taskId) {
|
|
2144
2130
|
const task = db.prepare('SELECT task_config FROM scheduled_tasks WHERE id = ? AND user_id = ?')
|
|
2145
2131
|
.get(taskId, userId);
|
|
2146
2132
|
if (task?.task_config) {
|
|
@@ -2169,7 +2155,7 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2169
2155
|
addCandidate(fallbackTarget);
|
|
2170
2156
|
|
|
2171
2157
|
if (candidateTargets.length === 0) {
|
|
2172
|
-
throw new Error('No messaging target is configured for this
|
|
2158
|
+
throw new Error('No messaging target is configured for this task run. Connect a platform and send at least one message on this server, or recreate the task after reconnecting.');
|
|
2173
2159
|
}
|
|
2174
2160
|
|
|
2175
2161
|
let lastError = null;
|
|
@@ -2208,20 +2194,21 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2208
2194
|
}
|
|
2209
2195
|
}
|
|
2210
2196
|
|
|
2211
|
-
throw (lastError || new Error('Failed to deliver
|
|
2197
|
+
throw (lastError || new Error('Failed to deliver task notification.'));
|
|
2212
2198
|
}
|
|
2213
2199
|
|
|
2214
2200
|
engine.emit(userId, 'run:interim', { runId, message });
|
|
2215
2201
|
return { sent: true, via: 'interim' };
|
|
2216
2202
|
}
|
|
2217
2203
|
|
|
2218
|
-
case '
|
|
2219
|
-
const s =
|
|
2220
|
-
if (!s) return { error: '
|
|
2204
|
+
case 'create_task': {
|
|
2205
|
+
const s = taskRuntime();
|
|
2206
|
+
if (!s) return { error: 'Task runtime not available' };
|
|
2221
2207
|
try {
|
|
2222
|
-
const task = s.createTask(userId, {
|
|
2208
|
+
const task = await s.createTask(userId, {
|
|
2223
2209
|
name: args.name,
|
|
2224
|
-
|
|
2210
|
+
triggerType: args.trigger_type,
|
|
2211
|
+
triggerConfig: args.trigger_config,
|
|
2225
2212
|
prompt: args.prompt,
|
|
2226
2213
|
enabled: args.enabled !== false,
|
|
2227
2214
|
model: args.model || null,
|
|
@@ -2229,46 +2216,25 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2229
2216
|
callGreeting: args.call_greeting || null,
|
|
2230
2217
|
agentId
|
|
2231
2218
|
});
|
|
2232
|
-
|
|
2233
|
-
return { success: true, task, message: `Scheduled task "${args.name}" created(${args.cron_expression}${callNote})` };
|
|
2234
|
-
} catch (err) {
|
|
2235
|
-
return { error: err.message };
|
|
2236
|
-
}
|
|
2237
|
-
}
|
|
2238
|
-
|
|
2239
|
-
case 'schedule_run': {
|
|
2240
|
-
const s = sched();
|
|
2241
|
-
if (!s) return { error: 'Scheduler not available' };
|
|
2242
|
-
try {
|
|
2243
|
-
const task = s.createTask(userId, {
|
|
2244
|
-
name: args.name,
|
|
2245
|
-
prompt: args.prompt,
|
|
2246
|
-
runAt: args.run_at,
|
|
2247
|
-
oneTime: true,
|
|
2248
|
-
model: args.model || null,
|
|
2249
|
-
callTo: args.call_to || null,
|
|
2250
|
-
callGreeting: args.call_greeting || null,
|
|
2251
|
-
agentId
|
|
2252
|
-
});
|
|
2253
|
-
return { success: true, task, message: `One-time run "${args.name}" scheduled for ${args.run_at}` };
|
|
2219
|
+
return { success: true, task, message: `Task "${args.name}" created.` };
|
|
2254
2220
|
} catch (err) {
|
|
2255
2221
|
return { error: err.message };
|
|
2256
2222
|
}
|
|
2257
2223
|
}
|
|
2258
2224
|
|
|
2259
|
-
case '
|
|
2260
|
-
const s =
|
|
2261
|
-
if (!s) return { error: '
|
|
2225
|
+
case 'list_tasks': {
|
|
2226
|
+
const s = taskRuntime();
|
|
2227
|
+
if (!s) return { error: 'Task runtime not available' };
|
|
2262
2228
|
const tasks = s.listTasks(userId).filter((task) => !agentId || task.agentId === agentId);
|
|
2263
2229
|
return { tasks, count: tasks.length };
|
|
2264
2230
|
}
|
|
2265
2231
|
|
|
2266
|
-
case '
|
|
2267
|
-
const s =
|
|
2268
|
-
if (!s) return { error: '
|
|
2232
|
+
case 'delete_task': {
|
|
2233
|
+
const s = taskRuntime();
|
|
2234
|
+
if (!s) return { error: 'Task runtime not available' };
|
|
2269
2235
|
try {
|
|
2270
2236
|
const task = db.prepare('SELECT agent_id FROM scheduled_tasks WHERE id = ? AND user_id = ?').get(args.task_id, userId);
|
|
2271
|
-
if (!task || task.agent_id !== agentId) return { error: '
|
|
2237
|
+
if (!task || task.agent_id !== agentId) return { error: 'Task not found for this agent.' };
|
|
2272
2238
|
s.deleteTask(args.task_id, userId);
|
|
2273
2239
|
return { success: true, deleted: args.task_id };
|
|
2274
2240
|
} catch (err) {
|
|
@@ -2276,21 +2242,22 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2276
2242
|
}
|
|
2277
2243
|
}
|
|
2278
2244
|
|
|
2279
|
-
case '
|
|
2280
|
-
const s =
|
|
2281
|
-
if (!s) return { error: '
|
|
2245
|
+
case 'update_task': {
|
|
2246
|
+
const s = taskRuntime();
|
|
2247
|
+
if (!s) return { error: 'Task runtime not available' };
|
|
2282
2248
|
try {
|
|
2283
2249
|
const existing = db.prepare('SELECT agent_id FROM scheduled_tasks WHERE id = ? AND user_id = ?').get(args.task_id, userId);
|
|
2284
|
-
if (!existing || existing.agent_id !== agentId) return { error: '
|
|
2250
|
+
if (!existing || existing.agent_id !== agentId) return { error: 'Task not found for this agent.' };
|
|
2285
2251
|
const updates = {};
|
|
2286
2252
|
if (args.name !== undefined) updates.name = args.name;
|
|
2287
|
-
if (args.
|
|
2253
|
+
if (args.trigger_type !== undefined) updates.triggerType = args.trigger_type;
|
|
2254
|
+
if (args.trigger_config !== undefined) updates.triggerConfig = args.trigger_config;
|
|
2288
2255
|
if (args.prompt !== undefined) updates.prompt = args.prompt;
|
|
2289
2256
|
if (args.enabled !== undefined) updates.enabled = args.enabled;
|
|
2290
2257
|
if (args.model !== undefined) updates.model = args.model || null;
|
|
2291
2258
|
if (args.call_to !== undefined) updates.callTo = args.call_to || null;
|
|
2292
2259
|
if (args.call_greeting !== undefined) updates.callGreeting = args.call_greeting || null;
|
|
2293
|
-
const updated = s.updateTask(args.task_id, userId, updates);
|
|
2260
|
+
const updated = await s.updateTask(args.task_id, userId, updates);
|
|
2294
2261
|
return { success: true, task: updated };
|
|
2295
2262
|
} catch (err) {
|
|
2296
2263
|
return { error: err.message };
|
|
@@ -2301,7 +2268,7 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2301
2268
|
const widgetService = widgets();
|
|
2302
2269
|
if (!widgetService) return { error: 'Widget service not available' };
|
|
2303
2270
|
try {
|
|
2304
|
-
const widget = widgetService.createWidget(userId, {
|
|
2271
|
+
const widget = await widgetService.createWidget(userId, {
|
|
2305
2272
|
name: args.name,
|
|
2306
2273
|
template: args.template,
|
|
2307
2274
|
layoutVariant: args.layout_variant,
|
|
@@ -2352,7 +2319,7 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2352
2319
|
if (!existing || existing.agentId !== agentId) {
|
|
2353
2320
|
return { error: 'Widget not found for this agent.' };
|
|
2354
2321
|
}
|
|
2355
|
-
const widget = widgetService.updateWidget(userId, args.widget_id, {
|
|
2322
|
+
const widget = await widgetService.updateWidget(userId, args.widget_id, {
|
|
2356
2323
|
name: args.name,
|
|
2357
2324
|
template: args.template,
|
|
2358
2325
|
layoutVariant: args.layout_variant,
|
|
@@ -214,7 +214,7 @@ class CommandRouter {
|
|
|
214
214
|
'- `/status` - show current run/queue status\n' +
|
|
215
215
|
'- `/tools` - list available built-in tools\n' +
|
|
216
216
|
'- `/memory` - quick memory summary\n' +
|
|
217
|
-
'- `/tasks` - list latest
|
|
217
|
+
'- `/tasks` - list latest task runs and configured tasks\n' +
|
|
218
218
|
'- `/models` - list currently available models\n' +
|
|
219
219
|
'- `/skills` - list enabled skills\n' +
|
|
220
220
|
'- `/help` - show this message\n\n' +
|
|
@@ -288,18 +288,26 @@ class CommandRouter {
|
|
|
288
288
|
|
|
289
289
|
if (mode === 'scheduled') {
|
|
290
290
|
const scheduled = db
|
|
291
|
-
.prepare('SELECT id, name, enabled,
|
|
291
|
+
.prepare('SELECT id, name, enabled, trigger_type, trigger_config, last_run FROM scheduled_tasks WHERE user_id = ? AND agent_id = ? ORDER BY created_at DESC')
|
|
292
292
|
.all(userId, agentId);
|
|
293
293
|
if (!scheduled.length) {
|
|
294
|
-
return { handled: true, content: '**Tasks**\n- No
|
|
294
|
+
return { handled: true, content: '**Tasks**\n- No tasks found.' };
|
|
295
295
|
}
|
|
296
296
|
const lines = scheduled.map((task) => {
|
|
297
|
-
|
|
298
|
-
|
|
297
|
+
let triggerLabel = task.trigger_type || 'schedule';
|
|
298
|
+
try {
|
|
299
|
+
const config = JSON.parse(task.trigger_config || '{}') || {};
|
|
300
|
+
if (triggerLabel === 'schedule') {
|
|
301
|
+
triggerLabel = config.mode === 'one_time'
|
|
302
|
+
? `one-time at ${config.runAt || 'unknown'}`
|
|
303
|
+
: (config.cronExpression || 'schedule');
|
|
304
|
+
}
|
|
305
|
+
} catch {}
|
|
306
|
+
return `- #${task.id} ${task.name} [${task.enabled ? 'enabled' : 'disabled'}] - ${triggerLabel}`;
|
|
299
307
|
});
|
|
300
308
|
return {
|
|
301
309
|
handled: true,
|
|
302
|
-
content: `**
|
|
310
|
+
content: `**Tasks (${scheduled.length})**\n${lines.join('\n')}`
|
|
303
311
|
};
|
|
304
312
|
}
|
|
305
313
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
|
+
const EventEmitter = require('events');
|
|
4
5
|
const fs = require('fs');
|
|
5
6
|
const path = require('path');
|
|
6
7
|
const db = require('../../../db/database');
|
|
@@ -205,8 +206,9 @@ function simplifyMessage(msg = {}, fallbackChatId = '') {
|
|
|
205
206
|
};
|
|
206
207
|
}
|
|
207
208
|
|
|
208
|
-
class WhatsAppPersonalProvider {
|
|
209
|
+
class WhatsAppPersonalProvider extends EventEmitter {
|
|
209
210
|
constructor(options = {}) {
|
|
211
|
+
super();
|
|
210
212
|
this.key = 'whatsapp_personal';
|
|
211
213
|
this.label = 'WhatsApp';
|
|
212
214
|
this.description =
|
|
@@ -779,6 +781,24 @@ class WhatsAppPersonalProvider {
|
|
|
779
781
|
...existingChat,
|
|
780
782
|
lastMessageAt: simplified.timestamp,
|
|
781
783
|
});
|
|
784
|
+
|
|
785
|
+
const userId = target.userId || null;
|
|
786
|
+
const agentId = target.agentId || null;
|
|
787
|
+
const connectionId = target.connectionId || null;
|
|
788
|
+
if (userId && agentId && connectionId && simplified.fromMe !== true) {
|
|
789
|
+
this.emit('message', {
|
|
790
|
+
userId,
|
|
791
|
+
agentId,
|
|
792
|
+
connectionId,
|
|
793
|
+
chatId: simplified.chatId,
|
|
794
|
+
sender: simplified.sender,
|
|
795
|
+
senderTag: simplified.senderTag,
|
|
796
|
+
messageId: simplified.id,
|
|
797
|
+
text: simplified.text,
|
|
798
|
+
timestamp: simplified.timestamp,
|
|
799
|
+
isGroup: String(simplified.chatId || '').endsWith('@g.us'),
|
|
800
|
+
});
|
|
801
|
+
}
|
|
782
802
|
}
|
|
783
803
|
|
|
784
804
|
_normalizeChatId(value) {
|
|
@@ -900,6 +920,8 @@ class WhatsAppPersonalProvider {
|
|
|
900
920
|
|
|
901
921
|
const client = existing || {
|
|
902
922
|
connectionId: connection.id,
|
|
923
|
+
userId: connection.user_id,
|
|
924
|
+
agentId: connection.agent_id,
|
|
903
925
|
authDir,
|
|
904
926
|
socket: null,
|
|
905
927
|
chats: new Map(),
|
|
@@ -10,7 +10,7 @@ const { MultiStepOrchestrator } = require('./ai/multiStep');
|
|
|
10
10
|
const { SkillRunner } = require('./ai/toolRunner');
|
|
11
11
|
const { CommandRouter } = require('./commands/router');
|
|
12
12
|
const { MessagingManager } = require('./messaging/manager');
|
|
13
|
-
const {
|
|
13
|
+
const { TaskRuntime } = require('./tasks/runtime');
|
|
14
14
|
const { WidgetService } = require('./widgets/service');
|
|
15
15
|
const { setupWebSocket } = require('./websocket');
|
|
16
16
|
const { registerMessagingAutomation } = require('./messaging/automation');
|
|
@@ -445,12 +445,12 @@ function restoreMcpClients(mcpClient) {
|
|
|
445
445
|
}
|
|
446
446
|
}
|
|
447
447
|
|
|
448
|
-
function
|
|
449
|
-
const
|
|
450
|
-
agentEngine.
|
|
451
|
-
|
|
452
|
-
logServiceReady('
|
|
453
|
-
return
|
|
448
|
+
function startTaskRuntime(app, io, agentEngine) {
|
|
449
|
+
const taskRuntime = registerLocal(app, 'taskRuntime', new TaskRuntime(io, agentEngine, app));
|
|
450
|
+
agentEngine.taskRuntime = taskRuntime;
|
|
451
|
+
taskRuntime.start();
|
|
452
|
+
logServiceReady('Task runtime started');
|
|
453
|
+
return taskRuntime;
|
|
454
454
|
}
|
|
455
455
|
|
|
456
456
|
function configureRealtime(app, io, services) {
|
|
@@ -459,7 +459,7 @@ function configureRealtime(app, io, services) {
|
|
|
459
459
|
messagingManager: services.messagingManager,
|
|
460
460
|
mcpClient: services.mcpClient,
|
|
461
461
|
integrationManager: services.integrationManager,
|
|
462
|
-
|
|
462
|
+
taskRuntime: services.taskRuntime,
|
|
463
463
|
recordingManager: services.recordingManager,
|
|
464
464
|
memoryManager: services.memoryManager,
|
|
465
465
|
voiceRuntimeManager: services.voiceRuntimeManager,
|
|
@@ -524,14 +524,14 @@ async function startServices(app, io) {
|
|
|
524
524
|
agentEngine,
|
|
525
525
|
});
|
|
526
526
|
|
|
527
|
-
const
|
|
527
|
+
const taskRuntime = startTaskRuntime(app, io, agentEngine);
|
|
528
528
|
|
|
529
529
|
configureRealtime(app, io, {
|
|
530
530
|
agentEngine,
|
|
531
531
|
messagingManager,
|
|
532
532
|
integrationManager,
|
|
533
533
|
mcpClient,
|
|
534
|
-
|
|
534
|
+
taskRuntime,
|
|
535
535
|
recordingManager,
|
|
536
536
|
memoryManager,
|
|
537
537
|
voiceRuntimeManager,
|
|
@@ -551,12 +551,12 @@ async function stopServices(app) {
|
|
|
551
551
|
const tasks = [];
|
|
552
552
|
console.log('[Services] Stopping services');
|
|
553
553
|
|
|
554
|
-
if (app.locals.
|
|
554
|
+
if (app.locals.taskRuntime) {
|
|
555
555
|
try {
|
|
556
|
-
app.locals.
|
|
557
|
-
logServiceReady('
|
|
556
|
+
app.locals.taskRuntime.stop();
|
|
557
|
+
logServiceReady('Task runtime stopped');
|
|
558
558
|
} catch (err) {
|
|
559
|
-
console.error('[
|
|
559
|
+
console.error('[Tasks] Stop error:', getErrorMessage(err));
|
|
560
560
|
}
|
|
561
561
|
}
|
|
562
562
|
|
|
@@ -897,15 +897,15 @@ class MemoryManager {
|
|
|
897
897
|
|
|
898
898
|
const queryTokens = tokenizeRecallQuery(query);
|
|
899
899
|
if (queryTokens.length) {
|
|
900
|
-
const
|
|
900
|
+
const recentTaskRuns = db.prepare(
|
|
901
901
|
`SELECT title, final_response, completed_at
|
|
902
902
|
FROM agent_runs
|
|
903
|
-
WHERE user_id = ? AND agent_id = ? AND trigger_source
|
|
903
|
+
WHERE user_id = ? AND agent_id = ? AND trigger_source IN ('schedule', 'tasks') AND status = 'completed'
|
|
904
904
|
ORDER BY completed_at DESC, created_at DESC
|
|
905
905
|
LIMIT 12`
|
|
906
906
|
).all(userId, agentId);
|
|
907
907
|
|
|
908
|
-
const
|
|
908
|
+
const taskMatches = recentTaskRuns
|
|
909
909
|
.map((run) => ({
|
|
910
910
|
...run,
|
|
911
911
|
score: scoreSchedulerRunMatch(queryTokens, run.title, run.final_response),
|
|
@@ -913,14 +913,14 @@ class MemoryManager {
|
|
|
913
913
|
.filter((run) => run.score > 0)
|
|
914
914
|
.slice(0, 3);
|
|
915
915
|
|
|
916
|
-
if (
|
|
917
|
-
const
|
|
916
|
+
if (taskMatches.length) {
|
|
917
|
+
const taskLines = taskMatches.map((run) => {
|
|
918
918
|
const when = run.completed_at ? String(run.completed_at) : 'unknown time';
|
|
919
|
-
const title = String(run.title || '
|
|
919
|
+
const title = String(run.title || 'task').replace(/\s+/g, ' ').trim();
|
|
920
920
|
const outcome = buildExcerpt(String(run.final_response || ''), query) || String(run.final_response || '').slice(0, 180);
|
|
921
921
|
return `- ${when}: ${title} -> ${outcome || '(no final response stored)'}`;
|
|
922
922
|
});
|
|
923
|
-
sections.push(`Relevant recent
|
|
923
|
+
sections.push(`Relevant recent task runs:\n${taskLines.join('\n')}`);
|
|
924
924
|
}
|
|
925
925
|
}
|
|
926
926
|
|
|
@@ -21,7 +21,7 @@ function isTransientOperationalMemory(content) {
|
|
|
21
21
|
|
|
22
22
|
const lower = text.toLowerCase();
|
|
23
23
|
const hasRecency = /\b(recent|latest|current|today|tonight|just now|this run|last run)\b/.test(lower);
|
|
24
|
-
const hasRunEntity = /\b(
|
|
24
|
+
const hasRunEntity = /\b(task|scheduled run|schedule run|agent run|task run|workflow run|job run)\b/.test(lower);
|
|
25
25
|
const hasExecutionState = /\b(status|completed|succeeded|failed|errored|finished|started|triggered|executed|ran)\b/.test(lower);
|
|
26
26
|
|
|
27
27
|
if (hasRecency && hasRunEntity && hasExecutionState) {
|
|
@@ -9,7 +9,6 @@ const { WhatsAppPlatform } = require('./whatsapp');
|
|
|
9
9
|
const { TelnyxVoicePlatform } = require('./telnyx');
|
|
10
10
|
const { DiscordPlatform } = require('./discord');
|
|
11
11
|
const { TelegramPlatform } = require('./telegram');
|
|
12
|
-
const { WaveshareWearablePlatform } = require('./waveshare_wearable');
|
|
13
12
|
const {
|
|
14
13
|
SlackPlatform,
|
|
15
14
|
GoogleChatPlatform,
|
|
@@ -89,7 +88,6 @@ class MessagingManager extends EventEmitter {
|
|
|
89
88
|
feishu: createGenericPlatformClass('feishu'),
|
|
90
89
|
line: LinePlatform,
|
|
91
90
|
mattermost: MattermostPlatform,
|
|
92
|
-
waveshare_wearable: WaveshareWearablePlatform,
|
|
93
91
|
nextcloud_talk: createGenericPlatformClass('nextcloud_talk'),
|
|
94
92
|
nostr: createGenericPlatformClass('nostr'),
|
|
95
93
|
synology_chat: createGenericPlatformClass('synology_chat'),
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
ensureOwnedIntegrationConnection,
|
|
5
|
+
normalizeBoolean,
|
|
6
|
+
normalizeTrimmedText,
|
|
7
|
+
} = require('../security');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
type: 'gmail_message_received',
|
|
11
|
+
label: 'Gmail Message Received',
|
|
12
|
+
providerKey: 'google_workspace',
|
|
13
|
+
appKey: 'gmail',
|
|
14
|
+
async validateConfig(config = {}, context = {}) {
|
|
15
|
+
const connection = ensureOwnedIntegrationConnection(context.integrationManager, {
|
|
16
|
+
userId: context.userId,
|
|
17
|
+
agentId: context.agentId,
|
|
18
|
+
connectionId: config.connectionId || config.connection_id,
|
|
19
|
+
providerKey: 'google_workspace',
|
|
20
|
+
appKey: 'gmail',
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
connectionId: connection.id,
|
|
24
|
+
accountEmail: connection.account_email || null,
|
|
25
|
+
query: normalizeTrimmedText(config.query, 500),
|
|
26
|
+
unreadOnly: normalizeBoolean(config.unreadOnly ?? config.unread_only, false),
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
summarize(config = {}) {
|
|
30
|
+
const parts = ['Gmail'];
|
|
31
|
+
if (config.accountEmail) parts.push(config.accountEmail);
|
|
32
|
+
if (config.query) parts.push(`query: ${config.query}`);
|
|
33
|
+
if (config.unreadOnly) parts.push('unread only');
|
|
34
|
+
return parts.join(' · ');
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = [
|
|
4
|
+
require('./schedule'),
|
|
5
|
+
require('./gmail_message_received'),
|
|
6
|
+
require('./outlook_email_received'),
|
|
7
|
+
require('./slack_message_received'),
|
|
8
|
+
require('./teams_message_received'),
|
|
9
|
+
require('./whatsapp_personal_message_received'),
|
|
10
|
+
];
|