neoagent 2.4.1-beta.32 → 2.4.1-beta.34
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/flutter_app/lib/main_models.dart +14 -0
- package/flutter_app/lib/main_operations.dart +117 -0
- package/package.json +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +34534 -34438
- package/server/services/ai/engine.js +49 -4
- package/server/services/ai/tools.js +12 -0
- package/server/services/tasks/integration_runtime.js +4 -1
- package/server/services/tasks/runtime.js +114 -43
- package/server/services/tasks/task_repository.js +35 -6
|
@@ -61,6 +61,17 @@ function generateTitle(task) {
|
|
|
61
61
|
return cleaned.slice(0, 90);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
function buildInitialRunMetadata(options = {}) {
|
|
65
|
+
const metadata = {};
|
|
66
|
+
if (options.taskId != null && String(options.taskId).trim()) {
|
|
67
|
+
metadata.taskId = options.taskId;
|
|
68
|
+
}
|
|
69
|
+
if (options.widgetId != null && String(options.widgetId).trim()) {
|
|
70
|
+
metadata.widgetId = options.widgetId;
|
|
71
|
+
}
|
|
72
|
+
return metadata;
|
|
73
|
+
}
|
|
74
|
+
|
|
64
75
|
function planningDepthForForceMode(forceMode) {
|
|
65
76
|
return forceMode === 'plan_execute' ? 'deep' : 'light';
|
|
66
77
|
}
|
|
@@ -1653,8 +1664,19 @@ class AgentEngine {
|
|
|
1653
1664
|
};
|
|
1654
1665
|
|
|
1655
1666
|
const runTitle = generateTitle(userMessage);
|
|
1656
|
-
|
|
1657
|
-
|
|
1667
|
+
const initialRunMetadata = buildInitialRunMetadata(options);
|
|
1668
|
+
db.prepare(`INSERT OR REPLACE INTO agent_runs(
|
|
1669
|
+
id, user_id, agent_id, title, status, trigger_type, trigger_source, model, metadata_json
|
|
1670
|
+
) VALUES(?, ?, ?, ?, 'running', ?, ?, ?, ?)`).run(
|
|
1671
|
+
runId,
|
|
1672
|
+
userId,
|
|
1673
|
+
agentId,
|
|
1674
|
+
runTitle,
|
|
1675
|
+
triggerType,
|
|
1676
|
+
triggerSource,
|
|
1677
|
+
model,
|
|
1678
|
+
Object.keys(initialRunMetadata).length ? JSON.stringify(initialRunMetadata) : null,
|
|
1679
|
+
);
|
|
1658
1680
|
|
|
1659
1681
|
const retryMessagingState = options.messagingRetryState || {};
|
|
1660
1682
|
const carriedVisibleMessage = String(retryMessagingState.lastVisibleMessage || '').trim();
|
|
@@ -1666,6 +1688,7 @@ class AgentEngine {
|
|
|
1666
1688
|
status: 'running',
|
|
1667
1689
|
aborted: false,
|
|
1668
1690
|
messagingSent: false,
|
|
1691
|
+
noResponse: false,
|
|
1669
1692
|
explicitMessageSent: carriedExplicitMessageSent,
|
|
1670
1693
|
lastSentMessage: carriedExplicitMessageSent ? carriedVisibleMessage : '',
|
|
1671
1694
|
sentMessages: [],
|
|
@@ -2199,7 +2222,17 @@ class AgentEngine {
|
|
|
2199
2222
|
break;
|
|
2200
2223
|
}
|
|
2201
2224
|
if (iteration < maxIterations) {
|
|
2202
|
-
const
|
|
2225
|
+
const proactiveRunNeedsDecision = (
|
|
2226
|
+
(triggerSource === 'schedule' || triggerSource === 'tasks')
|
|
2227
|
+
&& this.activeRuns.get(runId)?.noResponse !== true
|
|
2228
|
+
&& options.deliveryState?.noResponse !== true
|
|
2229
|
+
);
|
|
2230
|
+
const fallbackStatus = (
|
|
2231
|
+
proactiveRunNeedsDecision
|
|
2232
|
+
|| toolExecutions.length > 0
|
|
2233
|
+
|| failedStepCount > 0
|
|
2234
|
+
|| messagingSent
|
|
2235
|
+
) ? 'continue' : 'complete';
|
|
2203
2236
|
const loopState = await runWithModelFallback('loop decision', () => this.decideLoopState({
|
|
2204
2237
|
provider,
|
|
2205
2238
|
providerName,
|
|
@@ -2596,6 +2629,18 @@ class AgentEngine {
|
|
|
2596
2629
|
&& !messagingSent
|
|
2597
2630
|
&& runMeta?.widgetSnapshotSaved !== true
|
|
2598
2631
|
) {
|
|
2632
|
+
const explicitNoResponse = (
|
|
2633
|
+
runMeta?.noResponse === true
|
|
2634
|
+
|| options.deliveryState?.noResponse === true
|
|
2635
|
+
);
|
|
2636
|
+
if (
|
|
2637
|
+
(triggerSource === 'schedule' || triggerSource === 'tasks')
|
|
2638
|
+
&& !explicitNoResponse
|
|
2639
|
+
) {
|
|
2640
|
+
throw new Error(
|
|
2641
|
+
'Background run ended without producing a result or an explicit no-response decision.',
|
|
2642
|
+
);
|
|
2643
|
+
}
|
|
2599
2644
|
if (iteration >= maxIterations) {
|
|
2600
2645
|
throw new Error(`Iteration limit reached before explicit completion after ${maxIterations} iterations.`);
|
|
2601
2646
|
}
|
|
@@ -3358,4 +3403,4 @@ class AgentEngine {
|
|
|
3358
3403
|
}
|
|
3359
3404
|
}
|
|
3360
3405
|
|
|
3361
|
-
module.exports = { AgentEngine, getProviderForUser };
|
|
3406
|
+
module.exports = { AgentEngine, buildInitialRunMetadata, getProviderForUser };
|
|
@@ -274,6 +274,15 @@ function markProactiveMessageSent({ runState, deliveryState, content }) {
|
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
function markProactiveNoResponse({ runState, deliveryState }) {
|
|
278
|
+
if (runState) {
|
|
279
|
+
runState.noResponse = true;
|
|
280
|
+
}
|
|
281
|
+
if (deliveryState) {
|
|
282
|
+
deliveryState.noResponse = true;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
277
286
|
function normalizeStoredSettingString(value) {
|
|
278
287
|
if (value == null) return '';
|
|
279
288
|
if (typeof value !== 'string') return String(value || '').trim();
|
|
@@ -2139,6 +2148,9 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2139
2148
|
error: proactiveValidation.error,
|
|
2140
2149
|
};
|
|
2141
2150
|
}
|
|
2151
|
+
if (proactiveValidation.reason === 'no_response') {
|
|
2152
|
+
markProactiveNoResponse({ runState, deliveryState });
|
|
2153
|
+
}
|
|
2142
2154
|
return {
|
|
2143
2155
|
sent: false,
|
|
2144
2156
|
suppressed: proactiveValidation.suppressed === true,
|
|
@@ -275,7 +275,10 @@ async function pollIntegrationTask(runtime, task) {
|
|
|
275
275
|
const startIndex = rows.findIndex((row) => row.fingerprint === existingFingerprint);
|
|
276
276
|
const pending = startIndex >= 0 ? rows.slice(startIndex + 1) : rows.slice(-1);
|
|
277
277
|
for (const row of pending) {
|
|
278
|
-
await runtime.fireTaskFromTrigger(task.id, task.user_id, row);
|
|
278
|
+
const result = await runtime.fireTaskFromTrigger(task.id, task.user_id, row);
|
|
279
|
+
if (result?.error || (result?.skipped && result.reason !== 'duplicate_trigger')) {
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
279
282
|
}
|
|
280
283
|
}
|
|
281
284
|
|
|
@@ -62,11 +62,7 @@ function stringifyTaskResult(result) {
|
|
|
62
62
|
if (nested) return nested;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
return JSON.stringify(result, null, 2);
|
|
67
|
-
} catch {
|
|
68
|
-
return String(result || '');
|
|
69
|
-
}
|
|
65
|
+
return '';
|
|
70
66
|
}
|
|
71
67
|
|
|
72
68
|
class TaskRuntime {
|
|
@@ -189,6 +185,8 @@ class TaskRuntime {
|
|
|
189
185
|
manual: true,
|
|
190
186
|
triggerType: task.trigger_type || 'schedule',
|
|
191
187
|
triggerSource: 'manual',
|
|
188
|
+
}).catch((error) => {
|
|
189
|
+
console.error(`[Tasks] Manual task ${taskId} error:`, error.message);
|
|
192
190
|
});
|
|
193
191
|
return { running: true };
|
|
194
192
|
}
|
|
@@ -203,9 +201,8 @@ class TaskRuntime {
|
|
|
203
201
|
if (String(task.last_trigger_fingerprint || '') === fingerprint) {
|
|
204
202
|
return { skipped: true, reason: 'duplicate_trigger' };
|
|
205
203
|
}
|
|
206
|
-
this.taskRepository.markTaskTriggered(taskId, userId, fingerprint);
|
|
207
204
|
|
|
208
|
-
|
|
205
|
+
const result = await this._executeTask(taskId, userId, {
|
|
209
206
|
manual: false,
|
|
210
207
|
oneTime: false,
|
|
211
208
|
scheduledAt: triggerPayload.timestamp || new Date().toISOString(),
|
|
@@ -213,39 +210,58 @@ class TaskRuntime {
|
|
|
213
210
|
triggerSource: task.trigger_type || 'schedule',
|
|
214
211
|
triggerPayload: triggerPayload.context || {},
|
|
215
212
|
});
|
|
213
|
+
if (!result?.error && !result?.skipped) {
|
|
214
|
+
this.taskRepository.markTaskTriggered(taskId, userId, fingerprint);
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
_startOneTimePoller() {
|
|
219
220
|
this.oneTimePoller = cron.schedule('* * * * *', async () => {
|
|
220
|
-
|
|
221
|
+
try {
|
|
222
|
+
await this._runDueOneTimeTasks();
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error('[Tasks] One-time task poll failed:', error.message);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
221
228
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
229
|
+
async _runDueOneTimeTasks() {
|
|
230
|
+
const due = this.taskRepository.listDueOneTimeTasks();
|
|
231
|
+
|
|
232
|
+
for (const task of due) {
|
|
233
|
+
this.scheduleJobs.delete(task.id);
|
|
234
|
+
try {
|
|
235
|
+
const result = await this._executeTask(task.id, task.user_id, {
|
|
236
|
+
scheduledAt: task.run_at || new Date().toISOString(),
|
|
237
|
+
oneTime: true,
|
|
238
|
+
triggerType: 'schedule',
|
|
239
|
+
triggerSource: 'schedule',
|
|
240
|
+
});
|
|
241
|
+
if (result?.skipped) {
|
|
242
|
+
continue;
|
|
233
243
|
}
|
|
234
|
-
this.taskRepository.
|
|
244
|
+
this.taskRepository.deleteTask(task.id, task.user_id);
|
|
235
245
|
this.io.to(`user:${task.user_id}`).emit('tasks:task_deleted', { taskId: task.id });
|
|
246
|
+
} catch (err) {
|
|
247
|
+
console.error(`[Tasks] One-time task ${task.id} error:`, err.message);
|
|
236
248
|
}
|
|
237
|
-
}
|
|
249
|
+
}
|
|
238
250
|
}
|
|
239
251
|
|
|
240
252
|
_startIntegrationPoller() {
|
|
241
253
|
this.integrationPoller = cron.schedule(INTEGRATION_TRIGGER_POLL_CRON, async () => {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
254
|
+
try {
|
|
255
|
+
const tasks = this.taskRepository.listEnabledByTriggerTypes(POLLED_TRIGGER_TYPES);
|
|
256
|
+
for (const task of tasks) {
|
|
257
|
+
try {
|
|
258
|
+
await pollIntegrationTask(this, task);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.error(`[Tasks] Trigger poll failed for task ${task.id}:`, error.message);
|
|
261
|
+
}
|
|
248
262
|
}
|
|
263
|
+
} catch (error) {
|
|
264
|
+
console.error('[Tasks] Integration trigger poll failed:', error.message);
|
|
249
265
|
}
|
|
250
266
|
});
|
|
251
267
|
}
|
|
@@ -258,13 +274,17 @@ class TaskRuntime {
|
|
|
258
274
|
const cronExpression = String(triggerConfig.cronExpression || '').trim();
|
|
259
275
|
if (!cronExpression) return;
|
|
260
276
|
const job = cron.schedule(cronExpression, async () => {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
277
|
+
try {
|
|
278
|
+
await this._executeTask(task.id, task.user_id, {
|
|
279
|
+
scheduledAt: new Date().toISOString(),
|
|
280
|
+
manual: false,
|
|
281
|
+
oneTime: false,
|
|
282
|
+
triggerType: 'schedule',
|
|
283
|
+
triggerSource: 'schedule',
|
|
284
|
+
});
|
|
285
|
+
} catch (error) {
|
|
286
|
+
console.error(`[Tasks] Scheduled task ${task.id} error:`, error.message);
|
|
287
|
+
}
|
|
268
288
|
});
|
|
269
289
|
this.scheduleJobs.set(task.id, { task: job, userId: task.user_id });
|
|
270
290
|
}
|
|
@@ -324,6 +344,15 @@ class TaskRuntime {
|
|
|
324
344
|
this.taskRepository.markTaskRun(taskId, userId);
|
|
325
345
|
this.io.to(`user:${userId}`).emit('tasks:task_running', { taskId, timestamp: new Date().toISOString() });
|
|
326
346
|
|
|
347
|
+
let normalizedConfig = taskConfig;
|
|
348
|
+
const taskName = task.name || `Task ${taskId}`;
|
|
349
|
+
const deliveryState = {
|
|
350
|
+
messagingSent: false,
|
|
351
|
+
noResponse: false,
|
|
352
|
+
lastSentMessage: '',
|
|
353
|
+
sentMessages: [],
|
|
354
|
+
};
|
|
355
|
+
let completedRunId = null;
|
|
327
356
|
try {
|
|
328
357
|
if (task.task_type === 'widget_refresh') {
|
|
329
358
|
const widgetService = this.app?.locals?.widgetService;
|
|
@@ -339,8 +368,7 @@ class TaskRuntime {
|
|
|
339
368
|
return result;
|
|
340
369
|
}
|
|
341
370
|
|
|
342
|
-
|
|
343
|
-
const taskName = task.name || `Task ${taskId}`;
|
|
371
|
+
normalizedConfig = this._ensureDefaultNotifyTarget(userId, agentId, taskConfig, taskId);
|
|
344
372
|
const triggerSummary = this._summarizeTrigger(task.trigger_type, triggerConfig);
|
|
345
373
|
let notifyHint = '';
|
|
346
374
|
|
|
@@ -366,7 +394,6 @@ class TaskRuntime {
|
|
|
366
394
|
].filter(Boolean).join('\n\n');
|
|
367
395
|
|
|
368
396
|
const conversationId = this._getTaskConversation(userId, taskId, taskName, agentId);
|
|
369
|
-
const deliveryState = { messagingSent: false, lastSentMessage: '', sentMessages: [] };
|
|
370
397
|
let attempt = 0;
|
|
371
398
|
let recoveryNote = '';
|
|
372
399
|
while (attempt <= MAX_AUTONOMOUS_RETRIES) {
|
|
@@ -394,6 +421,7 @@ class TaskRuntime {
|
|
|
394
421
|
const result = typeof this.agentEngine.runWithModel === 'function'
|
|
395
422
|
? await this.agentEngine.runWithModel(userId, finalPrompt, runOptions, normalizedConfig.model || null)
|
|
396
423
|
: await this.agentEngine.run(userId, finalPrompt, runOptions);
|
|
424
|
+
completedRunId = result?.runId || null;
|
|
397
425
|
const fallbackDelivery = await this._deliverTaskResultIfNeeded({
|
|
398
426
|
userId,
|
|
399
427
|
agentId,
|
|
@@ -405,11 +433,30 @@ class TaskRuntime {
|
|
|
405
433
|
if (fallbackDelivery && result && typeof result === 'object') {
|
|
406
434
|
result.taskDelivery = fallbackDelivery;
|
|
407
435
|
}
|
|
436
|
+
if (fallbackDelivery?.error) {
|
|
437
|
+
const deliveryError = new Error(fallbackDelivery.error);
|
|
438
|
+
deliveryError.code = 'TASK_DELIVERY_FAILED';
|
|
439
|
+
throw deliveryError;
|
|
440
|
+
}
|
|
441
|
+
if (
|
|
442
|
+
!deliveryState.messagingSent
|
|
443
|
+
&& !deliveryState.noResponse
|
|
444
|
+
&& !stringifyTaskResult(result).trim()
|
|
445
|
+
) {
|
|
446
|
+
throw new Error(
|
|
447
|
+
'Background task completed without producing a result or an explicit no-response decision.',
|
|
448
|
+
);
|
|
449
|
+
}
|
|
408
450
|
this.io.to(`user:${userId}`).emit('tasks:task_complete', { taskId, result });
|
|
409
451
|
return result;
|
|
410
452
|
} catch (err) {
|
|
453
|
+
if (completedRunId) {
|
|
454
|
+
this.taskRepository.markAgentRunFailed(completedRunId, userId, err.message);
|
|
455
|
+
}
|
|
456
|
+
if (err?.code === 'TASK_DELIVERY_FAILED') throw err;
|
|
411
457
|
if (attempt >= MAX_AUTONOMOUS_RETRIES) throw err;
|
|
412
458
|
attempt += 1;
|
|
459
|
+
completedRunId = null;
|
|
413
460
|
recoveryNote = [
|
|
414
461
|
'\n\n[SYSTEM: Previous task attempt failed]',
|
|
415
462
|
`Error: ${String(err?.message || 'Unknown runtime error')}`,
|
|
@@ -424,12 +471,24 @@ class TaskRuntime {
|
|
|
424
471
|
}
|
|
425
472
|
} catch (err) {
|
|
426
473
|
console.error(`[Tasks] Task ${taskId} error:`, err.message);
|
|
474
|
+
if (err?.code !== 'TASK_DELIVERY_FAILED') {
|
|
475
|
+
await this._deliverTaskResultIfNeeded({
|
|
476
|
+
userId,
|
|
477
|
+
agentId,
|
|
478
|
+
taskId,
|
|
479
|
+
taskConfig: normalizedConfig,
|
|
480
|
+
result: {
|
|
481
|
+
content: `Background task "${taskName}" could not complete after retrying. Check the task run logs for details.`,
|
|
482
|
+
},
|
|
483
|
+
deliveryState,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
427
486
|
this.io.to(`user:${userId}`).emit('tasks:task_skipped', {
|
|
428
487
|
taskId,
|
|
429
488
|
reason: 'execution_failed',
|
|
430
489
|
timestamp: new Date().toISOString(),
|
|
431
490
|
});
|
|
432
|
-
return { skipped: false, error: err.message };
|
|
491
|
+
return { skipped: false, error: err.message, runId: completedRunId };
|
|
433
492
|
}
|
|
434
493
|
}
|
|
435
494
|
|
|
@@ -518,7 +577,10 @@ class TaskRuntime {
|
|
|
518
577
|
triggerSummary,
|
|
519
578
|
nextRun: triggerType === 'schedule' ? scheduleAdapter.nextRun(triggerConfig) : null,
|
|
520
579
|
enabled: !!row.enabled,
|
|
521
|
-
lastRun: row.last_run || null,
|
|
580
|
+
lastRun: row.last_run_started_at || row.last_run || null,
|
|
581
|
+
lastRunId: row.last_run_id || null,
|
|
582
|
+
lastRunStatus: row.last_run_status || null,
|
|
583
|
+
lastRunError: row.last_run_error || null,
|
|
522
584
|
lastTriggeredAt: row.last_triggered_at || null,
|
|
523
585
|
taskType: row.task_type || 'agent_prompt',
|
|
524
586
|
taskConfig,
|
|
@@ -618,13 +680,18 @@ class TaskRuntime {
|
|
|
618
680
|
result,
|
|
619
681
|
deliveryState,
|
|
620
682
|
}) {
|
|
621
|
-
if (deliveryState?.messagingSent || taskConfig.callTo) return null;
|
|
622
|
-
const manager = this.app?.locals?.messagingManager || this.agentEngine?.messagingManager || null;
|
|
623
|
-
if (!manager) return null;
|
|
624
|
-
|
|
683
|
+
if (deliveryState?.messagingSent || deliveryState?.noResponse || taskConfig.callTo) return null;
|
|
625
684
|
const targets = this._buildNotifyTargets(userId, agentId, taskConfig);
|
|
626
685
|
if (!targets.length) return null;
|
|
627
686
|
|
|
687
|
+
const manager = this.app?.locals?.messagingManager || this.agentEngine?.messagingManager || null;
|
|
688
|
+
if (!manager) {
|
|
689
|
+
return {
|
|
690
|
+
sent: false,
|
|
691
|
+
error: 'Messaging delivery is unavailable on this server.',
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
628
695
|
let lastError = null;
|
|
629
696
|
for (const target of targets) {
|
|
630
697
|
const message = normalizeOutgoingMessageForPlatform(
|
|
@@ -676,6 +743,10 @@ class TaskRuntime {
|
|
|
676
743
|
|
|
677
744
|
if (lastError) {
|
|
678
745
|
console.error(`[Tasks] Task ${taskId} notification delivery failed:`, lastError.message);
|
|
746
|
+
return {
|
|
747
|
+
sent: false,
|
|
748
|
+
error: lastError.message,
|
|
749
|
+
};
|
|
679
750
|
}
|
|
680
751
|
return null;
|
|
681
752
|
}
|
|
@@ -59,16 +59,34 @@ class TaskRepository {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
listTasksForAgent(userId, agentId, includeLegacyMainTasks) {
|
|
62
|
+
const select = `
|
|
63
|
+
SELECT
|
|
64
|
+
scheduled_tasks.*,
|
|
65
|
+
latest_run.id AS last_run_id,
|
|
66
|
+
latest_run.status AS last_run_status,
|
|
67
|
+
latest_run.error AS last_run_error,
|
|
68
|
+
latest_run.created_at AS last_run_started_at
|
|
69
|
+
FROM scheduled_tasks
|
|
70
|
+
LEFT JOIN agent_runs AS latest_run ON latest_run.id = (
|
|
71
|
+
SELECT candidate.id
|
|
72
|
+
FROM agent_runs AS candidate
|
|
73
|
+
WHERE candidate.user_id = scheduled_tasks.user_id
|
|
74
|
+
AND json_valid(candidate.metadata_json)
|
|
75
|
+
AND CAST(json_extract(candidate.metadata_json, '$.taskId') AS TEXT) = CAST(scheduled_tasks.id AS TEXT)
|
|
76
|
+
ORDER BY candidate.created_at DESC, candidate.rowid DESC
|
|
77
|
+
LIMIT 1
|
|
78
|
+
)`;
|
|
62
79
|
return includeLegacyMainTasks
|
|
63
80
|
? db.prepare(
|
|
64
|
-
|
|
65
|
-
WHERE user_id = ?
|
|
66
|
-
|
|
81
|
+
`${select}
|
|
82
|
+
WHERE scheduled_tasks.user_id = ?
|
|
83
|
+
AND (scheduled_tasks.agent_id = ? OR scheduled_tasks.agent_id IS NULL)
|
|
84
|
+
ORDER BY scheduled_tasks.created_at DESC`
|
|
67
85
|
).all(userId, agentId)
|
|
68
86
|
: db.prepare(
|
|
69
|
-
|
|
70
|
-
WHERE user_id = ? AND agent_id = ?
|
|
71
|
-
ORDER BY created_at DESC`
|
|
87
|
+
`${select}
|
|
88
|
+
WHERE scheduled_tasks.user_id = ? AND scheduled_tasks.agent_id = ?
|
|
89
|
+
ORDER BY scheduled_tasks.created_at DESC`
|
|
72
90
|
).all(userId, agentId);
|
|
73
91
|
}
|
|
74
92
|
|
|
@@ -126,6 +144,17 @@ class TaskRepository {
|
|
|
126
144
|
db.prepare('UPDATE scheduled_tasks SET last_run = datetime(\'now\') WHERE id = ? AND user_id = ?').run(taskId, userId);
|
|
127
145
|
}
|
|
128
146
|
|
|
147
|
+
markAgentRunFailed(runId, userId, error) {
|
|
148
|
+
db.prepare(
|
|
149
|
+
`UPDATE agent_runs
|
|
150
|
+
SET status = 'failed',
|
|
151
|
+
error = ?,
|
|
152
|
+
updated_at = datetime('now'),
|
|
153
|
+
completed_at = COALESCE(completed_at, datetime('now'))
|
|
154
|
+
WHERE id = ? AND user_id = ?`
|
|
155
|
+
).run(String(error || 'Task execution failed.'), runId, userId);
|
|
156
|
+
}
|
|
157
|
+
|
|
129
158
|
updateTaskConfig(taskId, userId, taskConfig) {
|
|
130
159
|
db.prepare('UPDATE scheduled_tasks SET task_config = ? WHERE id = ? AND user_id = ?')
|
|
131
160
|
.run(JSON.stringify(taskConfig), taskId, userId);
|