neoagent 2.3.1-beta.62 → 2.3.1-beta.64
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 +9 -0
- package/docs/configuration.md +11 -0
- package/flutter_app/lib/main.dart +2 -0
- package/flutter_app/lib/main_account_settings.dart +50 -22
- package/flutter_app/lib/main_admin.dart +24 -10
- package/flutter_app/lib/main_app_shell.dart +10 -9
- package/flutter_app/lib/main_chat.dart +433 -309
- package/flutter_app/lib/main_controller.dart +164 -6
- package/flutter_app/lib/main_integrations.dart +15 -7
- package/flutter_app/lib/main_models.dart +116 -7
- package/flutter_app/lib/main_navigation.dart +27 -18
- package/flutter_app/lib/main_operations.dart +162 -91
- package/flutter_app/lib/main_runtime.dart +22 -0
- package/flutter_app/lib/main_settings.dart +287 -75
- package/flutter_app/lib/main_shared.dart +165 -6
- package/flutter_app/lib/main_unified.dart +388 -0
- package/flutter_app/lib/src/analytics_service.dart +294 -0
- package/flutter_app/lib/src/backend_client.dart +4 -0
- package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +2 -0
- package/flutter_app/pubspec.lock +8 -0
- package/flutter_app/pubspec.yaml +1 -0
- package/flutter_app/web/index.html +1 -0
- package/package.json +1 -1
- package/server/config/analytics.js +30 -0
- package/server/db/database.js +52 -0
- package/server/http/routes.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/AssetManifest.bin +1 -1
- package/server/public/assets/AssetManifest.bin.json +1 -1
- package/server/public/assets/NOTICES +183 -0
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/assets/packages/mixpanel_flutter/assets/mixpanel.js +3 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/index.html +1 -0
- package/server/public/main.dart.js +83685 -82046
- package/server/routes/agents.js +2 -1
- package/server/routes/memory.js +75 -3
- package/server/routes/runtime.js +14 -0
- package/server/routes/widgets.js +4 -4
- package/server/services/ai/engine.js +86 -0
- package/server/services/ai/runEvents.js +100 -0
- package/server/services/memory/manager.js +242 -26
- package/server/services/websocket.js +3 -1
- package/server/services/widgets/focus_widget.js +126 -0
- package/server/services/widgets/service.js +130 -2
package/server/routes/agents.js
CHANGED
|
@@ -4,6 +4,7 @@ const db = require('../db/database');
|
|
|
4
4
|
const { requireAuth } = require('../middleware/auth');
|
|
5
5
|
const { sanitizeError } = require('../utils/security');
|
|
6
6
|
const { getAgentIdFromRequest, resolveAgentId } = require('../services/agents/manager');
|
|
7
|
+
const { listRunEvents } = require('../services/ai/runEvents');
|
|
7
8
|
const { isInterimAssistantMetadata } = require('../services/ai/interim');
|
|
8
9
|
const { buildAgentRunContext } = require('./_helpers/agentRunContext');
|
|
9
10
|
|
|
@@ -176,7 +177,7 @@ router.get('/:id/steps', (req, res) => {
|
|
|
176
177
|
|| run.final_response
|
|
177
178
|
|| null;
|
|
178
179
|
|
|
179
|
-
res.json({ run, steps, response });
|
|
180
|
+
res.json({ run, steps, events: listRunEvents(run.id), response });
|
|
180
181
|
});
|
|
181
182
|
|
|
182
183
|
// Abort a run
|
package/server/routes/memory.js
CHANGED
|
@@ -33,6 +33,59 @@ function findOwnedMemoryIds(db, userId, agentId, ids) {
|
|
|
33
33
|
).all(userId, agentId, ...ids).map((row) => row.id);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function parsePlainObject(input, fieldName) {
|
|
37
|
+
if (input == null) return null;
|
|
38
|
+
if (typeof input === 'string') {
|
|
39
|
+
try {
|
|
40
|
+
input = JSON.parse(input);
|
|
41
|
+
} catch {
|
|
42
|
+
throw new Error(`${fieldName} must be valid JSON.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
46
|
+
throw new Error(`${fieldName} must be an object.`);
|
|
47
|
+
}
|
|
48
|
+
return input;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeOptionalStringField(value, fieldName, maxLength, pattern = null) {
|
|
52
|
+
if (value == null || value === '') return null;
|
|
53
|
+
if (typeof value !== 'string') {
|
|
54
|
+
throw new Error(`${fieldName} must be a string.`);
|
|
55
|
+
}
|
|
56
|
+
const normalized = value.trim().slice(0, maxLength);
|
|
57
|
+
if (!normalized) return null;
|
|
58
|
+
if (pattern && !pattern.test(normalized)) {
|
|
59
|
+
throw new Error(`${fieldName} has an invalid format.`);
|
|
60
|
+
}
|
|
61
|
+
return normalized;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeSourceRef(input) {
|
|
65
|
+
const raw = parsePlainObject(input, 'sourceRef');
|
|
66
|
+
if (!raw) return undefined;
|
|
67
|
+
return {
|
|
68
|
+
sourceType: normalizeOptionalStringField(raw.sourceType ?? raw.type, 'sourceRef.sourceType', 48, /^[a-z0-9_:-]+$/i),
|
|
69
|
+
sourceId: normalizeOptionalStringField(raw.sourceId ?? raw.id, 'sourceRef.sourceId', 128),
|
|
70
|
+
sourceLabel: normalizeOptionalStringField(raw.sourceLabel ?? raw.label, 'sourceRef.sourceLabel', 160),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function normalizeScope(input) {
|
|
75
|
+
const raw = parsePlainObject(input, 'scope');
|
|
76
|
+
if (!raw) return undefined;
|
|
77
|
+
const scopeType = normalizeOptionalStringField(raw.scopeType ?? raw.type, 'scope.scopeType', 32, /^(agent|conversation|task|channel|shared)$/i);
|
|
78
|
+
return {
|
|
79
|
+
scopeType: scopeType ? scopeType.toLowerCase() : null,
|
|
80
|
+
scopeId: normalizeOptionalStringField(raw.scopeId ?? raw.id, 'scope.scopeId', 128),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function normalizeMetadata(input) {
|
|
85
|
+
const raw = parsePlainObject(input, 'metadata');
|
|
86
|
+
return raw == null ? undefined : raw;
|
|
87
|
+
}
|
|
88
|
+
|
|
36
89
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
37
90
|
// Overview (for initial page load)
|
|
38
91
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -46,6 +99,7 @@ router.get('/', (req, res) => {
|
|
|
46
99
|
res.json({
|
|
47
100
|
agentId,
|
|
48
101
|
assistantBehaviorNotes: mm.getAssistantBehaviorNotes(userId, { agentId }),
|
|
102
|
+
assistantSelfState: mm.getAssistantSelfState(userId, { agentId }),
|
|
49
103
|
dailyLogs: mm.listDailyLogs(7, userId),
|
|
50
104
|
apiKeys: Object.keys(mm.readApiKeys(userId)),
|
|
51
105
|
coreMemory
|
|
@@ -81,13 +135,31 @@ router.post('/memories', async (req, res) => {
|
|
|
81
135
|
const mm = req.app.locals.memoryManager;
|
|
82
136
|
const userId = req.session.userId;
|
|
83
137
|
const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
|
|
84
|
-
const { content, category = 'episodic', importance = 5 } = req.body;
|
|
138
|
+
const { content, category = 'episodic', importance = 5, sourceRef, scope, staleAfterDays, metadata } = req.body;
|
|
85
139
|
if (!content || !content.trim()) return res.status(400).json({ error: 'content is required' });
|
|
86
140
|
try {
|
|
87
|
-
|
|
141
|
+
let normalizedStaleAfterDays;
|
|
142
|
+
if (staleAfterDays != null && staleAfterDays !== '') {
|
|
143
|
+
normalizedStaleAfterDays = Number.parseInt(staleAfterDays, 10);
|
|
144
|
+
if (!Number.isInteger(normalizedStaleAfterDays) || normalizedStaleAfterDays <= 0) {
|
|
145
|
+
return res.status(400).json({ error: 'staleAfterDays must be a positive integer.' });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const id = await mm.saveMemory(userId, content, category, importance, {
|
|
150
|
+
agentId,
|
|
151
|
+
sourceRef: normalizeSourceRef(sourceRef),
|
|
152
|
+
scope: normalizeScope(scope),
|
|
153
|
+
staleAfterDays: normalizedStaleAfterDays,
|
|
154
|
+
metadata: normalizeMetadata(metadata),
|
|
155
|
+
});
|
|
88
156
|
res.json({ success: true, id });
|
|
89
157
|
} catch (err) {
|
|
90
|
-
|
|
158
|
+
const message = sanitizeError(err);
|
|
159
|
+
if (/must be valid JSON|must be an object|must be a string|has an invalid format/i.test(message)) {
|
|
160
|
+
return res.status(400).json({ error: message });
|
|
161
|
+
}
|
|
162
|
+
res.status(500).json({ error: message });
|
|
91
163
|
}
|
|
92
164
|
});
|
|
93
165
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const express = require('express');
|
|
4
|
+
const { getAnalyticsConfig } = require('../config/analytics');
|
|
5
|
+
|
|
6
|
+
const router = express.Router();
|
|
7
|
+
|
|
8
|
+
router.get('/config', (req, res) => {
|
|
9
|
+
res.json({
|
|
10
|
+
analytics: getAnalyticsConfig(),
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
module.exports = router;
|
package/server/routes/widgets.js
CHANGED
|
@@ -78,7 +78,7 @@ router.delete('/:id', (req, res) => {
|
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
router.post('/:id/refresh', (req, res) => {
|
|
81
|
+
router.post('/:id/refresh', async (req, res) => {
|
|
82
82
|
try {
|
|
83
83
|
const service = widgetService(req);
|
|
84
84
|
const taskRuntime = req.app?.locals?.taskRuntime;
|
|
@@ -89,10 +89,10 @@ router.post('/:id/refresh', (req, res) => {
|
|
|
89
89
|
if (!widget) {
|
|
90
90
|
return res.status(404).json({ error: 'Widget not found.' });
|
|
91
91
|
}
|
|
92
|
-
if (!widget.scheduledTaskId) {
|
|
93
|
-
return res.
|
|
92
|
+
if (widget.isSystem || !widget.scheduledTaskId) {
|
|
93
|
+
return res.json(await service.refreshWidget(req.session.userId, req.params.id));
|
|
94
94
|
}
|
|
95
|
-
res.json(taskRuntime.runTaskNow(widget.scheduledTaskId, req.session.userId));
|
|
95
|
+
res.json(await taskRuntime.runTaskNow(widget.scheduledTaskId, req.session.userId));
|
|
96
96
|
} catch (err) {
|
|
97
97
|
res.status(400).json({ error: sanitizeError(err) });
|
|
98
98
|
}
|
|
@@ -37,6 +37,7 @@ const {
|
|
|
37
37
|
buildInterimSignature,
|
|
38
38
|
normalizeInterimKind,
|
|
39
39
|
} = require('./interim');
|
|
40
|
+
const { recordRunEvent } = require('./runEvents');
|
|
40
41
|
|
|
41
42
|
const MAX_CONSECUTIVE_TOOL_FAILURES = 5;
|
|
42
43
|
const WIDGET_REFRESH_MAX_ITERATIONS = 30;
|
|
@@ -675,6 +676,22 @@ class AgentEngine {
|
|
|
675
676
|
.run(JSON.stringify(next), runId);
|
|
676
677
|
}
|
|
677
678
|
|
|
679
|
+
recordRunEvent(userId, runId, eventType, payload = {}, options = {}) {
|
|
680
|
+
try {
|
|
681
|
+
return recordRunEvent({
|
|
682
|
+
runId,
|
|
683
|
+
userId,
|
|
684
|
+
agentId: options.agentId || null,
|
|
685
|
+
eventType,
|
|
686
|
+
requestId: options.requestId || null,
|
|
687
|
+
stepId: options.stepId || null,
|
|
688
|
+
payload,
|
|
689
|
+
});
|
|
690
|
+
} catch {
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
678
695
|
async publishInterimUpdate({
|
|
679
696
|
userId,
|
|
680
697
|
runId,
|
|
@@ -1533,6 +1550,12 @@ class AgentEngine {
|
|
|
1533
1550
|
toolPids: new Set(),
|
|
1534
1551
|
});
|
|
1535
1552
|
this.emit(userId, 'run:start', { runId, agentId, title: runTitle, model, triggerType, triggerSource });
|
|
1553
|
+
this.recordRunEvent(userId, runId, 'run_started', {
|
|
1554
|
+
title: runTitle,
|
|
1555
|
+
model,
|
|
1556
|
+
triggerType,
|
|
1557
|
+
triggerSource,
|
|
1558
|
+
}, { agentId });
|
|
1536
1559
|
console.info(
|
|
1537
1560
|
`[Run ${shortenRunId(runId)}] started trigger=${triggerSource} type=${triggerType} model=${model} title=${summarizeForLog(runTitle, 120)}`
|
|
1538
1561
|
);
|
|
@@ -1593,6 +1616,11 @@ class AgentEngine {
|
|
|
1593
1616
|
if (threadStateMessage) {
|
|
1594
1617
|
messages.push({ role: 'system', content: threadStateMessage });
|
|
1595
1618
|
}
|
|
1619
|
+
this.recordRunEvent(userId, runId, 'memory_injected', {
|
|
1620
|
+
hasRecallContext: Boolean(recallMsg),
|
|
1621
|
+
hasThreadState: Boolean(threadStateMessage),
|
|
1622
|
+
recallPreview: recallMsg ? String(recallMsg).slice(0, 240) : '',
|
|
1623
|
+
}, { agentId });
|
|
1596
1624
|
messages.push(this.buildUserMessage(userMessage, options));
|
|
1597
1625
|
messages = sanitizeConversationMessages(messages);
|
|
1598
1626
|
|
|
@@ -1746,6 +1774,10 @@ class AgentEngine {
|
|
|
1746
1774
|
promptMetrics = this.mergePromptMetrics(promptMetrics, metrics, iteration, tools.length);
|
|
1747
1775
|
this.persistPromptMetrics(runId, promptMetrics).catch(() => { });
|
|
1748
1776
|
this.emit(userId, 'run:thinking', { runId, iteration });
|
|
1777
|
+
this.recordRunEvent(userId, runId, 'model_turn_started', {
|
|
1778
|
+
iteration,
|
|
1779
|
+
toolCount: tools.length,
|
|
1780
|
+
}, { agentId });
|
|
1749
1781
|
|
|
1750
1782
|
let response;
|
|
1751
1783
|
let responseModel = model;
|
|
@@ -1879,6 +1911,12 @@ class AgentEngine {
|
|
|
1879
1911
|
}
|
|
1880
1912
|
}
|
|
1881
1913
|
|
|
1914
|
+
this.recordRunEvent(userId, runId, 'model_turn_completed', {
|
|
1915
|
+
iteration,
|
|
1916
|
+
toolCallCount: response.toolCalls?.length || 0,
|
|
1917
|
+
contentPreview: String(lastContent || streamContent || '').slice(0, 240),
|
|
1918
|
+
}, { agentId });
|
|
1919
|
+
|
|
1882
1920
|
const assistantMessage = { role: 'assistant', content: lastContent };
|
|
1883
1921
|
if (response.toolCalls?.length) assistantMessage.tool_calls = response.toolCalls;
|
|
1884
1922
|
if (response.providerContentBlocks?.length) assistantMessage.providerContentBlocks = response.providerContentBlocks;
|
|
@@ -1972,6 +2010,12 @@ class AgentEngine {
|
|
|
1972
2010
|
runId, stepId, stepIndex, toolName, toolArgs,
|
|
1973
2011
|
type: this.getStepType(toolName)
|
|
1974
2012
|
});
|
|
2013
|
+
this.recordRunEvent(userId, runId, 'tool_started', {
|
|
2014
|
+
stepIndex,
|
|
2015
|
+
toolName,
|
|
2016
|
+
toolArgs,
|
|
2017
|
+
type: this.getStepType(toolName),
|
|
2018
|
+
}, { agentId, stepId });
|
|
1975
2019
|
console.info(
|
|
1976
2020
|
`[Run ${shortenRunId(runId)}] step=${stepIndex} start tool=${toolName} args=${summarizeForLog(toolArgs)}`
|
|
1977
2021
|
);
|
|
@@ -2006,11 +2050,24 @@ class AgentEngine {
|
|
|
2006
2050
|
.run(stepStatus, JSON.stringify(toolResult).slice(0, 20000), toolErrorMessage || null, screenshotPath, stepId);
|
|
2007
2051
|
if (toolErrorMessage) {
|
|
2008
2052
|
this.emit(userId, 'run:tool_end', { runId, stepId, toolName, error: toolErrorMessage, result: toolResult, screenshotPath, status: stepStatus });
|
|
2053
|
+
this.recordRunEvent(userId, runId, 'tool_failed', {
|
|
2054
|
+
toolName,
|
|
2055
|
+
status: stepStatus,
|
|
2056
|
+
error: toolErrorMessage,
|
|
2057
|
+
durationMs: Date.now() - stepStartedAt,
|
|
2058
|
+
resultPreview: summarizeForLog(toolResult),
|
|
2059
|
+
}, { agentId, stepId });
|
|
2009
2060
|
console.warn(
|
|
2010
2061
|
`[Run ${shortenRunId(runId)}] step=${stepIndex} failed tool=${toolName} durationMs=${Date.now() - stepStartedAt} error=${summarizeForLog(toolErrorMessage, 160)}`
|
|
2011
2062
|
);
|
|
2012
2063
|
} else {
|
|
2013
2064
|
this.emit(userId, 'run:tool_end', { runId, stepId, toolName, result: toolResult, screenshotPath, status: stepStatus });
|
|
2065
|
+
this.recordRunEvent(userId, runId, 'tool_completed', {
|
|
2066
|
+
toolName,
|
|
2067
|
+
status: stepStatus,
|
|
2068
|
+
durationMs: Date.now() - stepStartedAt,
|
|
2069
|
+
resultPreview: summarizeForLog(toolResult),
|
|
2070
|
+
}, { agentId, stepId });
|
|
2014
2071
|
console.info(
|
|
2015
2072
|
`[Run ${shortenRunId(runId)}] step=${stepIndex} done tool=${toolName} status=${stepStatus} durationMs=${Date.now() - stepStartedAt} result=${summarizeForLog(toolResult)}`
|
|
2016
2073
|
);
|
|
@@ -2023,6 +2080,12 @@ class AgentEngine {
|
|
|
2023
2080
|
db.prepare('UPDATE agent_steps SET status = ?, error = ?, completed_at = datetime(\'now\') WHERE id = ?')
|
|
2024
2081
|
.run('failed', err.message, stepId);
|
|
2025
2082
|
this.emit(userId, 'run:tool_end', { runId, stepId, toolName, error: err.message, status: 'failed' });
|
|
2083
|
+
this.recordRunEvent(userId, runId, 'tool_failed', {
|
|
2084
|
+
toolName,
|
|
2085
|
+
status: 'failed',
|
|
2086
|
+
error: err.message,
|
|
2087
|
+
durationMs: Date.now() - stepStartedAt,
|
|
2088
|
+
}, { agentId, stepId });
|
|
2026
2089
|
console.warn(
|
|
2027
2090
|
`[Run ${shortenRunId(runId)}] step=${stepIndex} failed tool=${toolName} durationMs=${Date.now() - stepStartedAt} error=${summarizeForLog(err.message, 160)}`
|
|
2028
2091
|
);
|
|
@@ -2134,6 +2197,11 @@ class AgentEngine {
|
|
|
2134
2197
|
);
|
|
2135
2198
|
this.activeRuns.delete(runId);
|
|
2136
2199
|
this.emit(userId, 'run:stopped', { runId, triggerSource });
|
|
2200
|
+
this.recordRunEvent(userId, runId, 'run_stopped', {
|
|
2201
|
+
triggerSource,
|
|
2202
|
+
totalTokens,
|
|
2203
|
+
iterations: iteration,
|
|
2204
|
+
}, { agentId });
|
|
2137
2205
|
return { runId, content: '', totalTokens, iterations: iteration, status: 'stopped' };
|
|
2138
2206
|
}
|
|
2139
2207
|
|
|
@@ -2337,6 +2405,14 @@ class AgentEngine {
|
|
|
2337
2405
|
executionMode: analysis?.mode || 'execute',
|
|
2338
2406
|
verificationStatus: verification?.status || 'skipped',
|
|
2339
2407
|
});
|
|
2408
|
+
this.recordRunEvent(userId, runId, 'run_completed', {
|
|
2409
|
+
contentPreview: String(finalResponseText || lastContent || '').slice(0, 240),
|
|
2410
|
+
totalTokens,
|
|
2411
|
+
iterations: iteration,
|
|
2412
|
+
triggerSource,
|
|
2413
|
+
executionMode: analysis?.mode || 'execute',
|
|
2414
|
+
verificationStatus: verification?.status || 'skipped',
|
|
2415
|
+
}, { agentId });
|
|
2340
2416
|
|
|
2341
2417
|
return { runId, content: lastContent, totalTokens, iterations: iteration, status: 'completed' };
|
|
2342
2418
|
} catch (err) {
|
|
@@ -2349,6 +2425,11 @@ class AgentEngine {
|
|
|
2349
2425
|
this.cleanupSubagentsForRun(runId, { cancelRunning: true });
|
|
2350
2426
|
this.activeRuns.delete(runId);
|
|
2351
2427
|
this.emit(userId, 'run:stopped', { runId, triggerSource });
|
|
2428
|
+
this.recordRunEvent(userId, runId, 'run_stopped', {
|
|
2429
|
+
triggerSource,
|
|
2430
|
+
totalTokens,
|
|
2431
|
+
iterations: iteration,
|
|
2432
|
+
}, { agentId });
|
|
2352
2433
|
return { runId, content: '', totalTokens, iterations: iteration, status: 'stopped' };
|
|
2353
2434
|
}
|
|
2354
2435
|
|
|
@@ -2478,6 +2559,11 @@ class AgentEngine {
|
|
|
2478
2559
|
this.cleanupSubagentsForRun(runId, { cancelRunning: true });
|
|
2479
2560
|
this.activeRuns.delete(runId);
|
|
2480
2561
|
this.emit(userId, 'run:error', { runId, error: err.message });
|
|
2562
|
+
this.recordRunEvent(userId, runId, 'run_failed', {
|
|
2563
|
+
error: err.message,
|
|
2564
|
+
totalTokens,
|
|
2565
|
+
iterations: iteration,
|
|
2566
|
+
}, { agentId });
|
|
2481
2567
|
|
|
2482
2568
|
if (messagingFailureContent) {
|
|
2483
2569
|
return {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const db = require('../../db/database');
|
|
4
|
+
|
|
5
|
+
function parseJsonObject(value, fallback = {}) {
|
|
6
|
+
if (!value) return { ...fallback };
|
|
7
|
+
if (typeof value === 'object' && !Array.isArray(value)) return { ...value };
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(String(value));
|
|
10
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
11
|
+
? parsed
|
|
12
|
+
: { ...fallback };
|
|
13
|
+
} catch {
|
|
14
|
+
return { ...fallback };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function recordRunEvent({
|
|
19
|
+
runId,
|
|
20
|
+
userId,
|
|
21
|
+
agentId = null,
|
|
22
|
+
eventType,
|
|
23
|
+
requestId = null,
|
|
24
|
+
stepId = null,
|
|
25
|
+
sequenceIndex = null,
|
|
26
|
+
payload = {},
|
|
27
|
+
}) {
|
|
28
|
+
if (!runId || !userId || !eventType) return null;
|
|
29
|
+
const payloadJson = JSON.stringify(
|
|
30
|
+
payload && typeof payload === 'object' && !Array.isArray(payload) ? payload : {},
|
|
31
|
+
);
|
|
32
|
+
const row = db.transaction(() => {
|
|
33
|
+
const resolvedSequence = Number.isInteger(sequenceIndex) && sequenceIndex > 0
|
|
34
|
+
? sequenceIndex
|
|
35
|
+
: Number(
|
|
36
|
+
db.prepare(
|
|
37
|
+
'SELECT COALESCE(MAX(sequence_index), 0) AS max_sequence FROM agent_run_events WHERE run_id = ?'
|
|
38
|
+
).get(runId)?.max_sequence || 0,
|
|
39
|
+
) + 1;
|
|
40
|
+
const result = db.prepare(
|
|
41
|
+
`INSERT INTO agent_run_events (
|
|
42
|
+
run_id, user_id, agent_id, event_type, request_id, step_id, sequence_index, payload_json
|
|
43
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
44
|
+
).run(
|
|
45
|
+
runId,
|
|
46
|
+
userId,
|
|
47
|
+
agentId || null,
|
|
48
|
+
eventType,
|
|
49
|
+
requestId || null,
|
|
50
|
+
stepId || null,
|
|
51
|
+
resolvedSequence,
|
|
52
|
+
payloadJson,
|
|
53
|
+
);
|
|
54
|
+
return db.prepare(
|
|
55
|
+
`SELECT id, run_id, user_id, agent_id, event_type, request_id, step_id, sequence_index, payload_json, created_at
|
|
56
|
+
FROM agent_run_events
|
|
57
|
+
WHERE id = ?`
|
|
58
|
+
).get(result.lastInsertRowid);
|
|
59
|
+
})();
|
|
60
|
+
|
|
61
|
+
return row ? {
|
|
62
|
+
id: Number(row.id),
|
|
63
|
+
runId: row.run_id,
|
|
64
|
+
userId: row.user_id,
|
|
65
|
+
agentId: row.agent_id || null,
|
|
66
|
+
eventType: row.event_type,
|
|
67
|
+
requestId: row.request_id || null,
|
|
68
|
+
stepId: row.step_id || null,
|
|
69
|
+
sequenceIndex: Number(row.sequence_index || 0),
|
|
70
|
+
payload: parseJsonObject(row.payload_json, {}),
|
|
71
|
+
createdAt: row.created_at,
|
|
72
|
+
} : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function listRunEvents(runId) {
|
|
76
|
+
if (!runId) return [];
|
|
77
|
+
const rows = db.prepare(
|
|
78
|
+
`SELECT id, run_id, user_id, agent_id, event_type, request_id, step_id, sequence_index, payload_json, created_at
|
|
79
|
+
FROM agent_run_events
|
|
80
|
+
WHERE run_id = ?
|
|
81
|
+
ORDER BY sequence_index ASC, id ASC`
|
|
82
|
+
).all(runId);
|
|
83
|
+
return rows.map((row) => ({
|
|
84
|
+
id: Number(row.id),
|
|
85
|
+
runId: row.run_id,
|
|
86
|
+
userId: row.user_id,
|
|
87
|
+
agentId: row.agent_id || null,
|
|
88
|
+
eventType: row.event_type,
|
|
89
|
+
requestId: row.request_id || null,
|
|
90
|
+
stepId: row.step_id || null,
|
|
91
|
+
sequenceIndex: Number(row.sequence_index || 0),
|
|
92
|
+
payload: parseJsonObject(row.payload_json, {}),
|
|
93
|
+
createdAt: row.created_at,
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
recordRunEvent,
|
|
99
|
+
listRunEvents,
|
|
100
|
+
};
|