agentboss 0.1.3 → 0.1.4

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.
@@ -25,7 +25,7 @@
25
25
  } catch (e) {}
26
26
  })();
27
27
  </script>
28
- <script type="module" crossorigin src="/assets/index-CT8rBVfX.js"></script>
28
+ <script type="module" crossorigin src="/assets/index-sks7Tuv7.js"></script>
29
29
  <link rel="stylesheet" crossorigin href="/assets/index-C1wFD_Vo.css">
30
30
  </head>
31
31
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentboss",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "AI Agent collaboration analytics - become your AI agent's boss, not its babysitter",
5
5
  "main": "server/index.js",
6
6
  "bin": {
@@ -15,6 +15,7 @@ const {
15
15
  getDailySummaries,
16
16
  getAnalysisState,
17
17
  getOverviewTopProjects,
18
+ getUserMessageCountsByDateRange,
18
19
  } = require('../db/queries');
19
20
  const { mapTopProjects } = require('../utils/project');
20
21
 
@@ -202,11 +203,19 @@ function buildStats(db, fromDate, toDate) {
202
203
  */
203
204
  function buildSessionList(db, fromDate, toDate) {
204
205
  const sessions = getSessionsByDateRange(db, fromDate, toDate, undefined, 10000, 0);
206
+ // One aggregate query for the whole range — cheaper than N getMessagesBySession.
207
+ const userMsgCounts = getUserMessageCountsByDateRange(db, fromDate, toDate);
205
208
  const list = [];
206
209
 
207
210
  for (const s of sessions) {
208
211
  if (s.parent_session_id) continue; // skip subagents
209
212
  const analysis = getAnalysisBySession(db, s.id);
213
+ const userMessageCount = userMsgCounts.get(s.id) || 0;
214
+ const messageCount = s.message_count || 0;
215
+ // message_count = user + assistant turns (per ETL), so assistant is the
216
+ // remainder; clamp at 0 in case of edge-case data.
217
+ const assistantMessageCount = Math.max(0, messageCount - userMessageCount);
218
+ const activeMinutes = s.active_minutes || 0;
210
219
  list.push({
211
220
  id: s.id,
212
221
  title: s.title || '(untitled)',
@@ -216,6 +225,10 @@ function buildSessionList(db, fromDate, toDate) {
216
225
  startedAt: s.started_at,
217
226
  cost: Math.round((s.cost_usd || 0) * 100) / 100,
218
227
  duration: s.duration_minutes || 0,
228
+ activeMinutes,
229
+ userMessageCount,
230
+ assistantMessageCount,
231
+ messageCount,
219
232
  // v2 main-axis scores (UI averages H1/H2/H3/ENV/O1 into a single
220
233
  // composite column). ENV is derived from E1 + E2 client-side via
221
234
  // its own column would explode the row width.
@@ -228,6 +241,20 @@ function buildSessionList(db, fromDate, toDate) {
228
241
  });
229
242
  }
230
243
 
244
+ // Composite "投入度" sort: userMessageCount × activeMinutes, desc.
245
+ // Both factors are non-negative; multiplying keeps sessions that are
246
+ // weak on either axis (no user turns OR no active time) at the bottom
247
+ // without needing min-max normalisation that would shift on every
248
+ // filter change. Tie-break by started_at desc so the existing
249
+ // chronological order shows through when investment is equal (e.g.
250
+ // brand-new sessions with 0 active minutes).
251
+ list.sort((a, b) => {
252
+ const sa = (a.userMessageCount || 0) * (a.activeMinutes || 0);
253
+ const sb = (b.userMessageCount || 0) * (b.activeMinutes || 0);
254
+ if (sb !== sa) return sb - sa;
255
+ return (b.startedAt || '').localeCompare(a.startedAt || '');
256
+ });
257
+
231
258
  return list;
232
259
  }
233
260
 
@@ -402,7 +429,7 @@ function buildSessionDetail(db, sessionId) {
402
429
  // Per-tool sample invocations (up to N most recent) for UI hover details.
403
430
  // We fetch a flat list ordered by tool then time-desc, then bucket in JS so
404
431
  // we don't issue one query per tool.
405
- const SAMPLE_LIMIT_PER_TOOL = 5;
432
+ const SAMPLE_LIMIT_PER_TOOL = 100;
406
433
  const allCalls = queryAll(
407
434
  db,
408
435
  `SELECT tool_name, status, error_message, target_file, timestamp
@@ -241,7 +241,7 @@ module.exports = function (db) {
241
241
  // lookups don't collide with run-id lookups.
242
242
  // -------------------------------------------------------------------------
243
243
 
244
- router.get('/preview', (req, res) => {
244
+ router.get('/preview', async (req, res) => {
245
245
  const sessionId = req.query.sessionId;
246
246
  const adviceKey = req.query.adviceKey;
247
247
  const executor = req.query.executor;
@@ -260,7 +260,7 @@ module.exports = function (db) {
260
260
  ok: false, error: { code: 'BAD_REQUEST', message: 'executor must be opencode or claude' },
261
261
  });
262
262
  }
263
- const r = job.previewExecution(db, { sessionId, adviceKey, executor });
263
+ const r = await job.previewExecution(db, { sessionId, adviceKey, executor });
264
264
  if (!r.ok) return failure(res, r.reason, r);
265
265
  res.json({
266
266
  ok: true,
@@ -277,7 +277,7 @@ module.exports = function (db) {
277
277
  });
278
278
  });
279
279
 
280
- router.get('/project/preview', (req, res) => {
280
+ router.get('/project/preview', async (req, res) => {
281
281
  const project = req.query.project;
282
282
  const scope = req.query.scope;
283
283
  const from = req.query.from || '';
@@ -304,7 +304,7 @@ module.exports = function (db) {
304
304
  ok: false, error: { code: 'BAD_REQUEST', message: 'executor must be opencode or claude' },
305
305
  });
306
306
  }
307
- const r = job.previewProjectExecution(db, {
307
+ const r = await job.previewProjectExecution(db, {
308
308
  project, scope, windowFrom: from, windowTo: to, adviceKey, executor,
309
309
  });
310
310
  if (!r.ok) return failure(res, r.reason, r);
@@ -32,6 +32,7 @@ const {
32
32
  getOverviewErrorRate,
33
33
  getOverviewTopTools,
34
34
  getEarliestSessionDate,
35
+ getMessageRoleCounts,
35
36
  } = require('../db/queries');
36
37
  const { getCurrentDimensionsV2 } = require('../analysis/report-builder');
37
38
  const { canonicalProject, mapTopProjects } = require('../utils/project');
@@ -65,19 +66,26 @@ function daysAgo(n) {
65
66
  * @param {Object[]} rows
66
67
  * @returns {Object[]}
67
68
  */
68
- function mapRecentSessions(rows) {
69
- return rows.map((r) => ({
70
- id: r.id,
71
- source: r.source,
72
- startedAt: r.started_at,
73
- project: r.project,
74
- title: r.title,
75
- model: r.model,
76
- messageCount: r.message_count || 0,
77
- cost: Math.round((r.cost_usd || 0) * 10000) / 10000,
78
- errorCount: r.error_count || 0,
79
- reverted: !!r.reverted,
80
- }));
69
+ function mapRecentSessions(rows, roleCounts) {
70
+ return rows.map((r) => {
71
+ // Prefer real message-row counts (user + assistant) over the ETL-stored
72
+ // message_count, which can over-count and disagree with the detail page.
73
+ const rc = (roleCounts && roleCounts.get(r.id)) || { user: 0, assistant: 0 };
74
+ return {
75
+ id: r.id,
76
+ source: r.source,
77
+ startedAt: r.started_at,
78
+ project: r.project,
79
+ title: r.title,
80
+ model: r.model,
81
+ userMessageCount: rc.user,
82
+ assistantMessageCount: rc.assistant,
83
+ messageCount: (rc.user + rc.assistant) || r.message_count || 0,
84
+ cost: Math.round((r.cost_usd || 0) * 10000) / 10000,
85
+ errorCount: r.error_count || 0,
86
+ reverted: !!r.reverted,
87
+ };
88
+ });
81
89
  }
82
90
 
83
91
  /**
@@ -288,7 +296,10 @@ module.exports = function (db) {
288
296
  rows: timeseries,
289
297
  },
290
298
  topProjects: mapTopProjects(topProjectsRaw, TOP_N),
291
- recentSessions: mapRecentSessions(recentSessions),
299
+ recentSessions: mapRecentSessions(
300
+ recentSessions,
301
+ getMessageRoleCounts(db, recentSessions.map((r) => r.id))
302
+ ),
292
303
  topTools,
293
304
  capabilityRadar,
294
305
  },
@@ -1,119 +1,139 @@
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
- };
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
+ const { invalidateAdviceSettingsCache } = require('../llm/advice');
19
+ const { invalidateAnalyzerSettingsCache } = require('../llm/session-analyzer');
20
+ const { invalidateProjectAdviceSettingsCache } = require('../llm/project-advice');
21
+ const { _resetCache: resetCliDetectionCache } = require('../llm/cli-runner');
22
+
23
+ // Settings keys that invalidate per-module caches when they change.
24
+ const SETTINGS_CACHE_KEYS = new Set(['enable_llm_judge', 'llm_tool_preference']);
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Routes
28
+ // ---------------------------------------------------------------------------
29
+
30
+ /**
31
+ * Create the settings router with database access.
32
+ *
33
+ * @param {object} db sql.js Database instance
34
+ * @returns {import('express').Router}
35
+ */
36
+ module.exports = function (db) {
37
+ // GET /api/settings
38
+ router.get('/', (_req, res) => {
39
+ try {
40
+ const settings = getAllSettings(db);
41
+ const tools = getAllToolConfigs(db);
42
+
43
+ res.json({
44
+ ok: true,
45
+ data: { settings, tools },
46
+ meta: {
47
+ generated_at: new Date().toISOString(),
48
+ },
49
+ });
50
+ } catch (err) {
51
+ res.status(500).json({
52
+ ok: false,
53
+ error: { code: 'SETTINGS_ERROR', message: err.message },
54
+ });
55
+ }
56
+ });
57
+
58
+ // PUT /api/settings
59
+ router.put('/', (req, res) => {
60
+ try {
61
+ const body = req.body;
62
+ if (!body || typeof body !== 'object') {
63
+ return res.status(400).json({
64
+ ok: false,
65
+ error: { code: 'BAD_REQUEST', message: 'Request body must be a JSON object of {key: value} pairs' },
66
+ });
67
+ }
68
+
69
+ const keys = Object.keys(body);
70
+ for (const key of keys) {
71
+ setSetting(db, key, String(body[key]));
72
+ }
73
+
74
+ // Invalidate every LLM module's settings cache so toggles
75
+ // (`enable_llm_judge`) and CLI picks (`llm_tool_preference`) take
76
+ // effect immediately. Also drop the cli-runner detection cache so
77
+ // a switched preference doesn't reuse an old `detectAvailableCli`.
78
+ if (keys.some((k) => SETTINGS_CACHE_KEYS.has(k))) {
79
+ invalidateSettingsCache();
80
+ invalidateAdviceSettingsCache();
81
+ invalidateAnalyzerSettingsCache();
82
+ invalidateProjectAdviceSettingsCache();
83
+ }
84
+ if (keys.includes('llm_tool_preference')) {
85
+ resetCliDetectionCache();
86
+ }
87
+
88
+ const settings = getAllSettings(db);
89
+
90
+ res.json({
91
+ ok: true,
92
+ data: { settings, updated: keys },
93
+ meta: {
94
+ generated_at: new Date().toISOString(),
95
+ },
96
+ });
97
+ } catch (err) {
98
+ res.status(500).json({
99
+ ok: false,
100
+ error: { code: 'SETTINGS_ERROR', message: err.message },
101
+ });
102
+ }
103
+ });
104
+
105
+ // GET /api/settings/llm/diagnose — detect available CLI for LLM judge.
106
+ // Returns { available, name, active, preference, source, detected[] }
107
+ // so the Settings page can render every CLI's install status plus
108
+ // the user's current pick.
109
+ router.get('/llm/diagnose', async (_req, res) => {
110
+ try {
111
+ const info = await diagnoseLlm(db);
112
+ res.json({ ok: true, data: info, meta: { generated_at: new Date().toISOString() } });
113
+ } catch (err) {
114
+ res.status(500).json({ ok: false, error: { code: 'LLM_DIAGNOSE_ERROR', message: err.message } });
115
+ }
116
+ });
117
+
118
+ // GET /api/settings/tools
119
+ router.get('/tools', (_req, res) => {
120
+ try {
121
+ const data = getAllToolConfigs(db);
122
+
123
+ res.json({
124
+ ok: true,
125
+ data,
126
+ meta: {
127
+ generated_at: new Date().toISOString(),
128
+ },
129
+ });
130
+ } catch (err) {
131
+ res.status(500).json({
132
+ ok: false,
133
+ error: { code: 'SETTINGS_ERROR', message: err.message },
134
+ });
135
+ }
136
+ });
137
+
138
+ return router;
139
+ };