@ian2018cs/agenthub 0.1.25 → 0.1.27

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/dist/index.html CHANGED
@@ -25,7 +25,7 @@
25
25
 
26
26
  <!-- Prevent zoom on iOS -->
27
27
  <meta name="format-detection" content="telephone=no" />
28
- <script type="module" crossorigin src="/assets/index-1dh35QcB.js"></script>
28
+ <script type="module" crossorigin src="/assets/index-CdP2lKlj.js"></script>
29
29
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-BeVl62c0.js">
30
30
  <link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-C_VWDoZS.js">
31
31
  <link rel="modulepreload" crossorigin href="/assets/vendor-utils-00TdZexr.js">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ian2018cs/agenthub",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "A web-based UI for AI Agents",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
@@ -44,7 +44,7 @@
44
44
  "access": "public"
45
45
  },
46
46
  "dependencies": {
47
- "@anthropic-ai/claude-agent-sdk": "^0.2.50",
47
+ "@anthropic-ai/claude-agent-sdk": "^0.2.61",
48
48
  "@codemirror/lang-css": "^6.3.1",
49
49
  "@codemirror/lang-html": "^6.4.9",
50
50
  "@codemirror/lang-javascript": "^6.2.4",
@@ -263,6 +263,18 @@ function getAllSessions() {
263
263
  return Array.from(activeSessions.keys());
264
264
  }
265
265
 
266
+ // Periodic cleanup of stale sessions (every 5 minutes, remove sessions older than 2 hours)
267
+ const SESSION_MAX_AGE_MS = 2 * 60 * 60 * 1000;
268
+ setInterval(() => {
269
+ const now = Date.now();
270
+ for (const [sessionId, session] of activeSessions) {
271
+ if (now - session.startTime > SESSION_MAX_AGE_MS) {
272
+ console.log(`[WARN] Cleaning up stale active session: ${sessionId} (age: ${Math.round((now - session.startTime) / 60000)}min)`);
273
+ activeSessions.delete(sessionId);
274
+ }
275
+ }
276
+ }, 5 * 60 * 1000);
277
+
266
278
  /**
267
279
  * Transforms SDK messages to WebSocket format expected by frontend
268
280
  * @param {Object} sdkMessage - SDK message object
@@ -662,32 +674,32 @@ async function queryClaudeSDK(command, options = {}, ws) {
662
674
  cacheCreationTokens: hasPreciseCacheData ? undefined : cacheCreationTokens
663
675
  });
664
676
 
665
- // Insert usage record
666
- usageDb.insertRecord({
667
- user_uuid: userUuid,
668
- session_id: capturedSessionId,
669
- model: normalizedModel,
670
- raw_model: modelKey,
671
- input_tokens: inputTokens,
672
- output_tokens: outputTokens,
673
- cache_read_tokens: cacheReadTokens,
674
- cache_creation_tokens: cacheCreationTokens,
675
- cost_usd: cost,
676
- source: 'sdk'
677
- });
678
-
679
- // Update daily summary
677
+ // Atomically insert usage record + update daily summary in a single transaction
680
678
  const today = new Date().toISOString().split('T')[0];
681
- usageDb.upsertDailySummary({
682
- user_uuid: userUuid,
683
- date: today,
684
- model: normalizedModel,
685
- total_input_tokens: inputTokens,
686
- total_output_tokens: outputTokens,
687
- total_cost_usd: cost,
688
- session_count: 0, // Session count updated separately
689
- request_count: 1
690
- });
679
+ usageDb.recordUsageTransaction(
680
+ {
681
+ user_uuid: userUuid,
682
+ session_id: capturedSessionId,
683
+ model: normalizedModel,
684
+ raw_model: modelKey,
685
+ input_tokens: inputTokens,
686
+ output_tokens: outputTokens,
687
+ cache_read_tokens: cacheReadTokens,
688
+ cache_creation_tokens: cacheCreationTokens,
689
+ cost_usd: cost,
690
+ source: 'sdk'
691
+ },
692
+ {
693
+ user_uuid: userUuid,
694
+ date: today,
695
+ model: normalizedModel,
696
+ total_input_tokens: inputTokens,
697
+ total_output_tokens: outputTokens,
698
+ total_cost_usd: cost,
699
+ session_count: 0, // Session count updated separately
700
+ request_count: 1
701
+ }
702
+ );
691
703
 
692
704
  console.log(`Recorded usage for user ${userUuid}: ${normalizedModel}, cost: $${cost.toFixed(6)}`);
693
705
  }
@@ -45,6 +45,11 @@ try {
45
45
  // Create database connection
46
46
  const db = new Database(DB_PATH);
47
47
 
48
+ // Optimize SQLite for concurrent access
49
+ db.pragma('journal_mode = WAL'); // Write-Ahead Logging for concurrent reads during writes
50
+ db.pragma('busy_timeout = 5000'); // Wait up to 5s when database is locked instead of failing immediately
51
+ db.pragma('synchronous = NORMAL'); // Balance between safety and performance with WAL
52
+
48
53
  // Show app installation path prominently
49
54
  const appInstallPath = path.join(__dirname, '../..');
50
55
  console.log('');
@@ -675,6 +680,48 @@ const usageDb = {
675
680
  }
676
681
  },
677
682
 
683
+ // Atomic transaction: insert usage record + upsert daily summary
684
+ recordUsageTransaction: db.transaction((record, summary) => {
685
+ const insertStmt = db.prepare(`
686
+ INSERT INTO usage_records (user_uuid, session_id, model, raw_model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, cost_usd, source, created_at)
687
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
688
+ `);
689
+ insertStmt.run(
690
+ record.user_uuid,
691
+ record.session_id || null,
692
+ record.model,
693
+ record.raw_model || null,
694
+ record.input_tokens || 0,
695
+ record.output_tokens || 0,
696
+ record.cache_read_tokens || 0,
697
+ record.cache_creation_tokens || 0,
698
+ record.cost_usd || 0,
699
+ record.source || 'sdk',
700
+ record.created_at || new Date().toISOString()
701
+ );
702
+
703
+ const upsertStmt = db.prepare(`
704
+ INSERT INTO usage_daily_summary (user_uuid, date, model, total_input_tokens, total_output_tokens, total_cost_usd, session_count, request_count)
705
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
706
+ ON CONFLICT(user_uuid, date, model) DO UPDATE SET
707
+ total_input_tokens = total_input_tokens + excluded.total_input_tokens,
708
+ total_output_tokens = total_output_tokens + excluded.total_output_tokens,
709
+ total_cost_usd = total_cost_usd + excluded.total_cost_usd,
710
+ session_count = session_count + excluded.session_count,
711
+ request_count = request_count + excluded.request_count
712
+ `);
713
+ upsertStmt.run(
714
+ summary.user_uuid,
715
+ summary.date,
716
+ summary.model,
717
+ summary.total_input_tokens || 0,
718
+ summary.total_output_tokens || 0,
719
+ summary.total_cost_usd || 0,
720
+ summary.session_count || 0,
721
+ summary.request_count || 0
722
+ );
723
+ }),
724
+
678
725
  // Get all users usage summary
679
726
  getAllUsersSummary: () => {
680
727
  try {