agentboss 0.1.0
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/README.md +34 -0
- package/bin/aboss.js +288 -0
- package/client/dist/assets/index-C1wFD_Vo.css +1 -0
- package/client/dist/assets/index-DBj1Ujlx.js +137 -0
- package/client/dist/index.html +34 -0
- package/package.json +64 -0
- package/server/analysis/daily-aggregator.js +258 -0
- package/server/analysis/difficulty.js +129 -0
- package/server/analysis/dimensions/ai-knowledge.js +172 -0
- package/server/analysis/dimensions/ai-tools.js +161 -0
- package/server/analysis/dimensions/judgement.js +107 -0
- package/server/analysis/dimensions/llm-merge.js +57 -0
- package/server/analysis/dimensions/output-quality.js +167 -0
- package/server/analysis/dimensions/problem-definition.js +104 -0
- package/server/analysis/dimensions/system-thinking.js +225 -0
- package/server/analysis/evidence-builder.js +104 -0
- package/server/analysis/job.js +273 -0
- package/server/analysis/report-builder.js +581 -0
- package/server/analysis/scoring-v2.js +72 -0
- package/server/analysis/text-signals.js +179 -0
- package/server/analysis/thresholds-v2.js +358 -0
- package/server/api/advice.js +124 -0
- package/server/api/analysis.js +141 -0
- package/server/api/execution.js +330 -0
- package/server/api/metrics.js +277 -0
- package/server/api/overview.js +308 -0
- package/server/api/project.js +255 -0
- package/server/api/reports.js +125 -0
- package/server/api/sessions.js +118 -0
- package/server/api/settings.js +119 -0
- package/server/db/connection.js +175 -0
- package/server/db/queries.js +1051 -0
- package/server/db/schema.js +487 -0
- package/server/etl/active-time.js +150 -0
- package/server/etl/backfill-subagents.js +178 -0
- package/server/etl/claude-code.js +826 -0
- package/server/etl/detect.js +341 -0
- package/server/etl/judge-filter.js +117 -0
- package/server/etl/opencode.js +606 -0
- package/server/execution/job.js +662 -0
- package/server/execution/prompt.js +227 -0
- package/server/execution/runner.js +218 -0
- package/server/index.js +94 -0
- package/server/llm/advice-prompt.js +339 -0
- package/server/llm/advice.js +384 -0
- package/server/llm/analysis-prompt.js +162 -0
- package/server/llm/cli-runner.js +249 -0
- package/server/llm/judge-prompts.js +179 -0
- package/server/llm/judge.js +118 -0
- package/server/llm/project-advice-prompt.js +332 -0
- package/server/llm/project-advice.js +491 -0
- package/server/llm/session-analyzer.js +122 -0
- package/server/utils/project.js +80 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project detail + project-level AI advice API.
|
|
3
|
+
*
|
|
4
|
+
* Routes
|
|
5
|
+
* ------
|
|
6
|
+
* GET /api/project/:key basic meta + sessions in window
|
|
7
|
+
* GET /api/project/:key/advice read cached project_advice
|
|
8
|
+
* POST /api/project/:key/advice body { force } trigger generation
|
|
9
|
+
*
|
|
10
|
+
* The `:key` path param is the URL-encoded project path (raw or canonical;
|
|
11
|
+
* the server canonicalises before any lookup). Windows-friendly via
|
|
12
|
+
* encodeURIComponent on the client side.
|
|
13
|
+
*
|
|
14
|
+
* Query params on the GET routes select the time window:
|
|
15
|
+
* scope=daily|weekly|all (default 'all')
|
|
16
|
+
* from=YYYY-MM-DD to=YYYY-MM-DD required when scope!=all
|
|
17
|
+
*
|
|
18
|
+
* Failure reasons from server/llm/project-advice.js are mapped to
|
|
19
|
+
* HTTP codes mirroring /api/advice's table.
|
|
20
|
+
*
|
|
21
|
+
* @author Felix
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
const router = require('express').Router();
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
generateProjectAdvice,
|
|
30
|
+
loadProjectAdvice,
|
|
31
|
+
listProjectAdviceCaches,
|
|
32
|
+
resolveProjectSessions,
|
|
33
|
+
attachSessionAdvice,
|
|
34
|
+
} = require('../llm/project-advice');
|
|
35
|
+
const { canonicalProject } = require('../utils/project');
|
|
36
|
+
const { queryOne } = require('../db/queries');
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Reason → HTTP
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
const REASON_TO_STATUS = {
|
|
43
|
+
'no-project': 400,
|
|
44
|
+
'no-window': 400,
|
|
45
|
+
'no-sessions': 404,
|
|
46
|
+
'no-session-advice': 409,
|
|
47
|
+
'llm-disabled': 409,
|
|
48
|
+
'no-cli': 409,
|
|
49
|
+
'timeout': 504,
|
|
50
|
+
'bad-json': 502,
|
|
51
|
+
'spawn-error': 500,
|
|
52
|
+
'exit-non-zero': 500,
|
|
53
|
+
'internal': 500,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const REASON_TO_MESSAGE = {
|
|
57
|
+
'no-project': '缺少 project',
|
|
58
|
+
'no-window': '该 scope 需要 from/to',
|
|
59
|
+
'no-sessions': '该时间段内项目没有任何会话',
|
|
60
|
+
'no-session-advice': '该项目下没有任何 session 已经生成单会话 advice;请先在 session 详情页生成',
|
|
61
|
+
'llm-disabled': 'LLM judging is disabled in settings',
|
|
62
|
+
'no-cli': 'No opencode/claude CLI detected on PATH',
|
|
63
|
+
'timeout': 'LLM call timed out',
|
|
64
|
+
'bad-json': 'LLM returned non-JSON output',
|
|
65
|
+
'spawn-error': 'Failed to spawn LLM CLI',
|
|
66
|
+
'exit-non-zero': 'LLM CLI exited with a non-zero status',
|
|
67
|
+
'internal': 'Internal project-advice generation error',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function failure(res, reason, extra) {
|
|
71
|
+
const status = REASON_TO_STATUS[reason] || 500;
|
|
72
|
+
const message = (extra && extra.error) || REASON_TO_MESSAGE[reason] || reason;
|
|
73
|
+
const body = {
|
|
74
|
+
ok: false,
|
|
75
|
+
error: { code: reason.toUpperCase().replace(/-/g, '_'), message },
|
|
76
|
+
};
|
|
77
|
+
if (extra && extra.meta) body.meta = extra.meta;
|
|
78
|
+
return res.status(status).json(body);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Window resolver — derives a concrete from/to from query string
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
function resolveWindow(query) {
|
|
86
|
+
const scope = (query.scope || 'all').toLowerCase();
|
|
87
|
+
if (scope === 'all') return { scope, windowFrom: '', windowTo: '' };
|
|
88
|
+
const from = query.from || '';
|
|
89
|
+
const to = query.to || '';
|
|
90
|
+
if (!from || !to) return null;
|
|
91
|
+
return { scope, windowFrom: from, windowTo: to };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Routes
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
module.exports = function (db) {
|
|
99
|
+
// GET /api/project/:key — meta, stats, and session list under this project.
|
|
100
|
+
router.get('/:key', (req, res) => {
|
|
101
|
+
const project = canonicalProject(decodeURIComponent(req.params.key || ''));
|
|
102
|
+
if (!project) return failure(res, 'no-project');
|
|
103
|
+
const window = resolveWindow(req.query);
|
|
104
|
+
if (!window) return failure(res, 'no-window');
|
|
105
|
+
|
|
106
|
+
const raw = resolveProjectSessions(
|
|
107
|
+
db, project, window.windowFrom, window.windowTo
|
|
108
|
+
);
|
|
109
|
+
const enriched = attachSessionAdvice(db, raw);
|
|
110
|
+
|
|
111
|
+
// Stats are computed across the FULL set including subagents — the
|
|
112
|
+
// money / errors / advice-eligibility numbers should match what the
|
|
113
|
+
// user actually spent / generated. The session list rendered to the
|
|
114
|
+
// UI below filters subagents out.
|
|
115
|
+
let cost = 0, errors = 0, withAdvice = 0;
|
|
116
|
+
for (const s of enriched) {
|
|
117
|
+
cost += Number(s.cost) || 0;
|
|
118
|
+
errors += Number(s.errorCount) || 0;
|
|
119
|
+
if (s.advice && s.advice.categories) withAdvice++;
|
|
120
|
+
}
|
|
121
|
+
const topLevel = enriched.filter((s) => !s.parentSessionId);
|
|
122
|
+
const subagentCount = enriched.length - topLevel.length;
|
|
123
|
+
|
|
124
|
+
// Earliest/latest dates seen across all unified_session rows for this
|
|
125
|
+
// project (regardless of the requested window) — used by the UI to show
|
|
126
|
+
// "项目首次出现 / 最近活跃".
|
|
127
|
+
const span = queryOne(
|
|
128
|
+
db,
|
|
129
|
+
// We can't filter by canonical project in SQL cheaply, so range across
|
|
130
|
+
// anything LIKE-ish then ignore false positives via canonicalProject
|
|
131
|
+
// — but for the *summary numbers* it's fine to scope to the same
|
|
132
|
+
// resolveProjectSessions output.
|
|
133
|
+
`SELECT MIN(started_at) AS first_at, MAX(started_at) AS last_at
|
|
134
|
+
FROM unified_session
|
|
135
|
+
WHERE id IN (${raw.map(() => '?').join(',') || 'NULL'})`,
|
|
136
|
+
raw.map((r) => r.id)
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return res.json({
|
|
140
|
+
ok: true,
|
|
141
|
+
data: {
|
|
142
|
+
project,
|
|
143
|
+
scope: window.scope,
|
|
144
|
+
windowFrom: window.windowFrom,
|
|
145
|
+
windowTo: window.windowTo,
|
|
146
|
+
// sessionCount = full count (incl. subagents) so stats match
|
|
147
|
+
// up; sessionsCountTopLevel = what the listed table shows.
|
|
148
|
+
sessionCount: enriched.length,
|
|
149
|
+
sessionsCountTopLevel: topLevel.length,
|
|
150
|
+
subagentCount,
|
|
151
|
+
sessionsWithAdvice: withAdvice,
|
|
152
|
+
missingAdvice: enriched.length - withAdvice,
|
|
153
|
+
cost: Math.round(cost * 10000) / 10000,
|
|
154
|
+
errors,
|
|
155
|
+
firstActivityAt: span ? span.first_at : null,
|
|
156
|
+
lastActivityAt: span ? span.last_at : null,
|
|
157
|
+
sessions: topLevel.map((s) => ({
|
|
158
|
+
id: s.id,
|
|
159
|
+
title: s.title,
|
|
160
|
+
model: s.model,
|
|
161
|
+
date: s.date,
|
|
162
|
+
cost: Math.round((s.cost || 0) * 10000) / 10000,
|
|
163
|
+
msgCount: s.msgCount,
|
|
164
|
+
errorCount: s.errorCount,
|
|
165
|
+
hasAdvice: !!(s.advice && s.advice.categories),
|
|
166
|
+
})),
|
|
167
|
+
},
|
|
168
|
+
meta: { generated_at: new Date().toISOString() },
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// GET /api/project/:key/advice/index
|
|
173
|
+
// Compact list of every (scope, window_from, window_to) for which a
|
|
174
|
+
// project_advice cache exists. The UI uses it to pick a default
|
|
175
|
+
// window that actually has data (avoids "looks empty after reload"
|
|
176
|
+
// when the cached window doesn't match the default).
|
|
177
|
+
router.get('/:key/advice/index', (req, res) => {
|
|
178
|
+
const project = canonicalProject(decodeURIComponent(req.params.key || ''));
|
|
179
|
+
if (!project) return failure(res, 'no-project');
|
|
180
|
+
const caches = listProjectAdviceCaches(db, project);
|
|
181
|
+
res.json({
|
|
182
|
+
ok: true,
|
|
183
|
+
data: { project, caches },
|
|
184
|
+
meta: { generated_at: new Date().toISOString() },
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// GET /api/project/:key/advice — read cached project_advice; never spawns CLI.
|
|
189
|
+
router.get('/:key/advice', (req, res) => {
|
|
190
|
+
const project = canonicalProject(decodeURIComponent(req.params.key || ''));
|
|
191
|
+
if (!project) return failure(res, 'no-project');
|
|
192
|
+
const window = resolveWindow(req.query);
|
|
193
|
+
if (!window) return failure(res, 'no-window');
|
|
194
|
+
|
|
195
|
+
const cached = loadProjectAdvice(
|
|
196
|
+
db, project, window.scope, window.windowFrom, window.windowTo
|
|
197
|
+
);
|
|
198
|
+
return res.json({
|
|
199
|
+
ok: true,
|
|
200
|
+
data: cached
|
|
201
|
+
? {
|
|
202
|
+
project,
|
|
203
|
+
scope: window.scope,
|
|
204
|
+
windowFrom: window.windowFrom,
|
|
205
|
+
windowTo: window.windowTo,
|
|
206
|
+
advice: cached.payload,
|
|
207
|
+
sessionCount: cached.sessionCount,
|
|
208
|
+
cachedAt: cached.cachedAt,
|
|
209
|
+
cli: cached.cli,
|
|
210
|
+
fromCache: true,
|
|
211
|
+
}
|
|
212
|
+
: {
|
|
213
|
+
project,
|
|
214
|
+
scope: window.scope,
|
|
215
|
+
windowFrom: window.windowFrom,
|
|
216
|
+
windowTo: window.windowTo,
|
|
217
|
+
advice: null,
|
|
218
|
+
fromCache: false,
|
|
219
|
+
cachedAt: null,
|
|
220
|
+
cli: null,
|
|
221
|
+
},
|
|
222
|
+
meta: { generated_at: new Date().toISOString() },
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// POST /api/project/:key/advice body { force?: boolean }
|
|
227
|
+
router.post('/:key/advice', async (req, res) => {
|
|
228
|
+
const project = canonicalProject(decodeURIComponent(req.params.key || ''));
|
|
229
|
+
if (!project) return failure(res, 'no-project');
|
|
230
|
+
const window = resolveWindow(req.query);
|
|
231
|
+
if (!window) return failure(res, 'no-window');
|
|
232
|
+
const force = req.body?.force === true || req.body?.force === '1';
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const result = await generateProjectAdvice(db, {
|
|
236
|
+
project,
|
|
237
|
+
scope: window.scope,
|
|
238
|
+
windowFrom: window.windowFrom,
|
|
239
|
+
windowTo: window.windowTo,
|
|
240
|
+
force,
|
|
241
|
+
});
|
|
242
|
+
if (!result.ok) return failure(res, result.reason, result);
|
|
243
|
+
|
|
244
|
+
return res.json({
|
|
245
|
+
ok: true,
|
|
246
|
+
data: result.data,
|
|
247
|
+
meta: { generated_at: new Date().toISOString() },
|
|
248
|
+
});
|
|
249
|
+
} catch (err) {
|
|
250
|
+
return failure(res, 'internal', { error: err && err.message });
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return router;
|
|
255
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Report API routes for Agent Boss.
|
|
3
|
+
*
|
|
4
|
+
* Provides endpoints for yesterday, weekly, monthly, and session-detail
|
|
5
|
+
* reports built from analyzed boss.db data.
|
|
6
|
+
*
|
|
7
|
+
* @author Felix
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const router = require('express').Router();
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
buildYesterdayReport,
|
|
14
|
+
buildWeeklyReport,
|
|
15
|
+
buildSessionDetail,
|
|
16
|
+
} = require('../analysis/report-builder');
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Helpers
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Return the Monday (YYYY-MM-DD) of the current ISO week.
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
function currentWeekMonday() {
|
|
27
|
+
const d = new Date();
|
|
28
|
+
const day = d.getDay(); // 0=Sun … 6=Sat
|
|
29
|
+
const diff = day === 0 ? 6 : day - 1;
|
|
30
|
+
d.setDate(d.getDate() - diff);
|
|
31
|
+
const y = d.getFullYear();
|
|
32
|
+
const m = String(d.getMonth() + 1).padStart(2, '0');
|
|
33
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
34
|
+
return `${y}-${m}-${dd}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Routes
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create the reports router with database access.
|
|
43
|
+
*
|
|
44
|
+
* @param {object} db sql.js Database instance
|
|
45
|
+
* @returns {import('express').Router}
|
|
46
|
+
*/
|
|
47
|
+
module.exports = function (db) {
|
|
48
|
+
// GET /api/reports/yesterday
|
|
49
|
+
router.get('/yesterday', (_req, res) => {
|
|
50
|
+
try {
|
|
51
|
+
const data = buildYesterdayReport(db);
|
|
52
|
+
res.json({
|
|
53
|
+
ok: true,
|
|
54
|
+
data,
|
|
55
|
+
meta: {
|
|
56
|
+
generated_at: new Date().toISOString(),
|
|
57
|
+
period: { from: data.date, to: data.date },
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
} catch (err) {
|
|
61
|
+
res.status(500).json({
|
|
62
|
+
ok: false,
|
|
63
|
+
error: { code: 'REPORT_ERROR', message: err.message },
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// GET /api/reports/weekly?week=YYYY-MM-DD
|
|
69
|
+
router.get('/weekly', (req, res) => {
|
|
70
|
+
try {
|
|
71
|
+
const weekStart = req.query.week || currentWeekMonday();
|
|
72
|
+
const data = buildWeeklyReport(db, weekStart);
|
|
73
|
+
res.json({
|
|
74
|
+
ok: true,
|
|
75
|
+
data,
|
|
76
|
+
meta: {
|
|
77
|
+
generated_at: new Date().toISOString(),
|
|
78
|
+
period: { from: data.weekStart, to: data.weekEnd },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
} catch (err) {
|
|
82
|
+
res.status(500).json({
|
|
83
|
+
ok: false,
|
|
84
|
+
error: { code: 'REPORT_ERROR', message: err.message },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// GET /api/reports/monthly?month=YYYY-MM
|
|
90
|
+
router.get('/monthly', (_req, res) => {
|
|
91
|
+
res.json({
|
|
92
|
+
ok: true,
|
|
93
|
+
data: null,
|
|
94
|
+
message: 'Coming soon',
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// GET /api/reports/session/:id
|
|
99
|
+
router.get('/session/:id', (req, res) => {
|
|
100
|
+
try {
|
|
101
|
+
const data = buildSessionDetail(db, req.params.id);
|
|
102
|
+
if (!data) {
|
|
103
|
+
return res.status(404).json({
|
|
104
|
+
ok: false,
|
|
105
|
+
error: { code: 'NOT_FOUND', message: 'Session not found' },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
res.json({
|
|
109
|
+
ok: true,
|
|
110
|
+
data,
|
|
111
|
+
meta: {
|
|
112
|
+
generated_at: new Date().toISOString(),
|
|
113
|
+
period: { from: data.date, to: data.date },
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
} catch (err) {
|
|
117
|
+
res.status(500).json({
|
|
118
|
+
ok: false,
|
|
119
|
+
error: { code: 'REPORT_ERROR', message: err.message },
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return router;
|
|
125
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session API routes for Agent Boss.
|
|
3
|
+
*
|
|
4
|
+
* Provides endpoints for fetching individual session details, messages,
|
|
5
|
+
* and tool calls.
|
|
6
|
+
*
|
|
7
|
+
* @author Felix
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const router = require('express').Router();
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
getSessionById,
|
|
14
|
+
getAnalysisBySession,
|
|
15
|
+
getMessagesBySession,
|
|
16
|
+
getToolCallsBySession,
|
|
17
|
+
} = require('../db/queries');
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Routes
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create the sessions router with database access.
|
|
25
|
+
*
|
|
26
|
+
* @param {object} db sql.js Database instance
|
|
27
|
+
* @returns {import('express').Router}
|
|
28
|
+
*/
|
|
29
|
+
module.exports = function (db) {
|
|
30
|
+
// GET /api/sessions/:id
|
|
31
|
+
router.get('/:id', (req, res) => {
|
|
32
|
+
try {
|
|
33
|
+
const session = getSessionById(db, req.params.id);
|
|
34
|
+
if (!session) {
|
|
35
|
+
return res.status(404).json({
|
|
36
|
+
ok: false,
|
|
37
|
+
error: { code: 'NOT_FOUND', message: 'Session not found' },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const analysis = getAnalysisBySession(db, req.params.id);
|
|
42
|
+
|
|
43
|
+
res.json({
|
|
44
|
+
ok: true,
|
|
45
|
+
data: { session, analysis },
|
|
46
|
+
meta: {
|
|
47
|
+
generated_at: new Date().toISOString(),
|
|
48
|
+
period: { from: session.date, to: session.date },
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
} catch (err) {
|
|
52
|
+
res.status(500).json({
|
|
53
|
+
ok: false,
|
|
54
|
+
error: { code: 'SESSION_ERROR', message: err.message },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// GET /api/sessions/:id/messages
|
|
60
|
+
router.get('/:id/messages', (req, res) => {
|
|
61
|
+
try {
|
|
62
|
+
const session = getSessionById(db, req.params.id);
|
|
63
|
+
if (!session) {
|
|
64
|
+
return res.status(404).json({
|
|
65
|
+
ok: false,
|
|
66
|
+
error: { code: 'NOT_FOUND', message: 'Session not found' },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const data = getMessagesBySession(db, req.params.id);
|
|
71
|
+
|
|
72
|
+
res.json({
|
|
73
|
+
ok: true,
|
|
74
|
+
data,
|
|
75
|
+
meta: {
|
|
76
|
+
generated_at: new Date().toISOString(),
|
|
77
|
+
period: { from: session.date, to: session.date },
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
} catch (err) {
|
|
81
|
+
res.status(500).json({
|
|
82
|
+
ok: false,
|
|
83
|
+
error: { code: 'SESSION_ERROR', message: err.message },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// GET /api/sessions/:id/tools
|
|
89
|
+
router.get('/:id/tools', (req, res) => {
|
|
90
|
+
try {
|
|
91
|
+
const session = getSessionById(db, req.params.id);
|
|
92
|
+
if (!session) {
|
|
93
|
+
return res.status(404).json({
|
|
94
|
+
ok: false,
|
|
95
|
+
error: { code: 'NOT_FOUND', message: 'Session not found' },
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const data = getToolCallsBySession(db, req.params.id);
|
|
100
|
+
|
|
101
|
+
res.json({
|
|
102
|
+
ok: true,
|
|
103
|
+
data,
|
|
104
|
+
meta: {
|
|
105
|
+
generated_at: new Date().toISOString(),
|
|
106
|
+
period: { from: session.date, to: session.date },
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
} catch (err) {
|
|
110
|
+
res.status(500).json({
|
|
111
|
+
ok: false,
|
|
112
|
+
error: { code: 'SESSION_ERROR', message: err.message },
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return router;
|
|
118
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings API routes for Agent Boss.
|
|
3
|
+
*
|
|
4
|
+
* Provides endpoints for reading/writing user settings and listing
|
|
5
|
+
* tool configurations (data source status).
|
|
6
|
+
*
|
|
7
|
+
* @author Felix
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const router = require('express').Router();
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
getAllSettings,
|
|
14
|
+
setSetting,
|
|
15
|
+
getAllToolConfigs,
|
|
16
|
+
} = require('../db/queries');
|
|
17
|
+
const { diagnose: diagnoseLlm, invalidateSettingsCache } = require('../llm/judge');
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Routes
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create the settings router with database access.
|
|
25
|
+
*
|
|
26
|
+
* @param {object} db sql.js Database instance
|
|
27
|
+
* @returns {import('express').Router}
|
|
28
|
+
*/
|
|
29
|
+
module.exports = function (db) {
|
|
30
|
+
// GET /api/settings
|
|
31
|
+
router.get('/', (_req, res) => {
|
|
32
|
+
try {
|
|
33
|
+
const settings = getAllSettings(db);
|
|
34
|
+
const tools = getAllToolConfigs(db);
|
|
35
|
+
|
|
36
|
+
res.json({
|
|
37
|
+
ok: true,
|
|
38
|
+
data: { settings, tools },
|
|
39
|
+
meta: {
|
|
40
|
+
generated_at: new Date().toISOString(),
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
} catch (err) {
|
|
44
|
+
res.status(500).json({
|
|
45
|
+
ok: false,
|
|
46
|
+
error: { code: 'SETTINGS_ERROR', message: err.message },
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// PUT /api/settings
|
|
52
|
+
router.put('/', (req, res) => {
|
|
53
|
+
try {
|
|
54
|
+
const body = req.body;
|
|
55
|
+
if (!body || typeof body !== 'object') {
|
|
56
|
+
return res.status(400).json({
|
|
57
|
+
ok: false,
|
|
58
|
+
error: { code: 'BAD_REQUEST', message: 'Request body must be a JSON object of {key: value} pairs' },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const keys = Object.keys(body);
|
|
63
|
+
for (const key of keys) {
|
|
64
|
+
setSetting(db, key, String(body[key]));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// v2: invalidate the LLM-judge settings cache so the toggle takes
|
|
68
|
+
// effect on the very next analysis pass.
|
|
69
|
+
if (keys.includes('enable_llm_judge')) invalidateSettingsCache();
|
|
70
|
+
|
|
71
|
+
const settings = getAllSettings(db);
|
|
72
|
+
|
|
73
|
+
res.json({
|
|
74
|
+
ok: true,
|
|
75
|
+
data: { settings, updated: keys },
|
|
76
|
+
meta: {
|
|
77
|
+
generated_at: new Date().toISOString(),
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
} catch (err) {
|
|
81
|
+
res.status(500).json({
|
|
82
|
+
ok: false,
|
|
83
|
+
error: { code: 'SETTINGS_ERROR', message: err.message },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// GET /api/settings/llm/diagnose — detect available CLI for LLM judge.
|
|
89
|
+
router.get('/llm/diagnose', async (_req, res) => {
|
|
90
|
+
try {
|
|
91
|
+
const info = await diagnoseLlm();
|
|
92
|
+
res.json({ ok: true, data: info, meta: { generated_at: new Date().toISOString() } });
|
|
93
|
+
} catch (err) {
|
|
94
|
+
res.status(500).json({ ok: false, error: { code: 'LLM_DIAGNOSE_ERROR', message: err.message } });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// GET /api/settings/tools
|
|
99
|
+
router.get('/tools', (_req, res) => {
|
|
100
|
+
try {
|
|
101
|
+
const data = getAllToolConfigs(db);
|
|
102
|
+
|
|
103
|
+
res.json({
|
|
104
|
+
ok: true,
|
|
105
|
+
data,
|
|
106
|
+
meta: {
|
|
107
|
+
generated_at: new Date().toISOString(),
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
} catch (err) {
|
|
111
|
+
res.status(500).json({
|
|
112
|
+
ok: false,
|
|
113
|
+
error: { code: 'SETTINGS_ERROR', message: err.message },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return router;
|
|
119
|
+
};
|