@ian2018cs/agenthub 0.1.49 → 0.1.50

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-BSCDGWeH.js"></script>
28
+ <script type="module" crossorigin src="/assets/index-D2E4W2Y9.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">
@@ -34,7 +34,7 @@
34
34
  <link rel="modulepreload" crossorigin href="/assets/vendor-markdown-VwNYkg_0.js">
35
35
  <link rel="modulepreload" crossorigin href="/assets/vendor-syntax-CdGaPJRS.js">
36
36
  <link rel="modulepreload" crossorigin href="/assets/vendor-xterm-CvdiG4-n.js">
37
- <link rel="stylesheet" crossorigin href="/assets/index-BhhJnwtA.css">
37
+ <link rel="stylesheet" crossorigin href="/assets/index-D3HUILHG.css">
38
38
  </head>
39
39
  <body>
40
40
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ian2018cs/agenthub",
3
- "version": "0.1.49",
3
+ "version": "0.1.50",
4
4
  "description": "A web-based UI for AI Agents",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
@@ -1175,6 +1175,10 @@ const feishuDb = {
1175
1175
  db.prepare('DELETE FROM feishu_bindings WHERE feishu_open_id = ?').run(feishuOpenId);
1176
1176
  },
1177
1177
 
1178
+ getBindingByUserUuid: (userUuid) => {
1179
+ return db.prepare('SELECT * FROM feishu_bindings WHERE user_uuid = ?').get(userUuid) || null;
1180
+ },
1181
+
1178
1182
  getSessionState: (feishuOpenId) => {
1179
1183
  return db.prepare('SELECT * FROM feishu_session_state WHERE feishu_open_id = ?').get(feishuOpenId) || null;
1180
1184
  },
package/server/index.js CHANGED
@@ -43,7 +43,7 @@ import pty from 'node-pty';
43
43
  import fetch from 'node-fetch';
44
44
  import mime from 'mime-types';
45
45
 
46
- import { getProjects, getSessions, getSessionMessages, renameProject, deleteSession, deleteProject, addProjectManually, extractProjectDirectory, clearProjectDirectoryCache } from './projects.js';
46
+ import { getProjects, getSessions, getSessionMessages, renameProject, deleteSession, deleteProject, addProjectManually, extractProjectDirectory, clearProjectDirectoryCache, updateProjectLastActivity } from './projects.js';
47
47
  import { queryClaudeSDK, abortClaudeSDKSession, isClaudeSDKSessionActive, getActiveClaudeSDKSessions, resolveToolApproval } from './claude-sdk.js';
48
48
  import authRoutes from './routes/auth.js';
49
49
  import mcpRoutes from './routes/mcp.js';
@@ -1278,6 +1278,11 @@ function handleCodexConnection(ws, userData) {
1278
1278
 
1279
1279
  console.log('🟢 Codex process started with PTY, PID:', codexProcess.pid);
1280
1280
 
1281
+ // Update project lastActivity so it sorts correctly in the sidebar
1282
+ if (userUuid) {
1283
+ updateProjectLastActivity(projectPath, userUuid).catch(() => {});
1284
+ }
1285
+
1281
1286
  codexPtySessionsMap.set(ptySessionKey, {
1282
1287
  pty: codexProcess,
1283
1288
  ws: ws,
@@ -1513,6 +1518,11 @@ function handleGeminiConnection(ws, userData) {
1513
1518
 
1514
1519
  console.log('🟢 Gemini process started with PTY, PID:', geminiProcess.pid);
1515
1520
 
1521
+ // Update project lastActivity so it sorts correctly in the sidebar
1522
+ if (userUuid) {
1523
+ updateProjectLastActivity(projectPath, userUuid).catch(() => {});
1524
+ }
1525
+
1516
1526
  geminiPtySessionsMap.set(ptySessionKey, {
1517
1527
  pty: geminiProcess,
1518
1528
  ws: ws,
@@ -334,6 +334,8 @@ async function getProjects(userUuid) {
334
334
  fullPath: actualProjectDir,
335
335
  isCustomName: !!projectConfig.displayName,
336
336
  isManuallyAdded: true,
337
+ createdAt: projectConfig.createdAt || null,
338
+ lastActivity: projectConfig.lastActivity || projectConfig.createdAt || null,
337
339
  sessions: []
338
340
  };
339
341
 
@@ -721,11 +723,19 @@ async function renameProject(projectName, newDisplayName, userUuid) {
721
723
  const config = await loadProjectConfig(userUuid);
722
724
 
723
725
  if (!newDisplayName || newDisplayName.trim() === '') {
724
- // Remove custom name if empty, will fall back to auto-generated
725
- delete config[projectName];
726
+ // Remove only the displayName, preserve manuallyAdded/originalPath/createdAt/lastActivity
727
+ if (config[projectName]) {
728
+ const { displayName, ...rest } = config[projectName];
729
+ if (Object.keys(rest).length === 0) {
730
+ delete config[projectName];
731
+ } else {
732
+ config[projectName] = rest;
733
+ }
734
+ }
726
735
  } else {
727
- // Set custom display name
736
+ // Merge displayName into existing entry to preserve manuallyAdded/originalPath/createdAt/lastActivity
728
737
  config[projectName] = {
738
+ ...config[projectName],
729
739
  displayName: newDisplayName.trim()
730
740
  };
731
741
  }
@@ -878,10 +888,14 @@ async function addProjectManually(projectPath, displayName = null, userUuid) {
878
888
  // Allow adding projects even if the directory exists - this enables tracking
879
889
  // existing Claude Code or Cursor projects in the UI
880
890
 
891
+ const createdAt = new Date().toISOString();
892
+
881
893
  // Add to config as manually added project
882
894
  config[projectName] = {
883
895
  manuallyAdded: true,
884
- originalPath: absolutePath
896
+ originalPath: absolutePath,
897
+ createdAt,
898
+ lastActivity: createdAt
885
899
  };
886
900
 
887
901
  if (displayName) {
@@ -897,10 +911,29 @@ async function addProjectManually(projectPath, displayName = null, userUuid) {
897
911
  fullPath: absolutePath,
898
912
  displayName: displayName || await generateDisplayName(projectName, absolutePath),
899
913
  isManuallyAdded: true,
914
+ createdAt,
915
+ lastActivity: createdAt,
900
916
  sessions: []
901
917
  };
902
918
  }
903
919
 
920
+ // Update lastActivity for a project by its absolute path (used by Gemini/Codex shell sessions)
921
+ async function updateProjectLastActivity(projectPath, userUuid) {
922
+ if (!userUuid || !projectPath) return;
923
+ try {
924
+ const absolutePath = path.resolve(projectPath);
925
+ const projectName = absolutePath.replace(/\//g, '-');
926
+ const config = await loadProjectConfig(userUuid);
927
+ if (config[projectName]) {
928
+ config[projectName].lastActivity = new Date().toISOString();
929
+ await saveProjectConfig(config, userUuid);
930
+ }
931
+ } catch (err) {
932
+ // Non-fatal - just log
933
+ console.warn('[WARN] Failed to update project lastActivity:', err.message);
934
+ }
935
+ }
936
+
904
937
  export {
905
938
  getProjects,
906
939
  getSessions,
@@ -914,5 +947,6 @@ export {
914
947
  loadProjectConfig,
915
948
  saveProjectConfig,
916
949
  extractProjectDirectory,
917
- clearProjectDirectoryCache
950
+ clearProjectDirectoryCache,
951
+ updateProjectLastActivity
918
952
  };
@@ -2,7 +2,7 @@ import express from 'express';
2
2
  import bcrypt from 'bcrypt';
3
3
  import jwt from 'jsonwebtoken';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
- import { userDb, verificationDb, domainWhitelistDb, usageDb, settingsDb } from '../database/db.js';
5
+ import { userDb, verificationDb, domainWhitelistDb, usageDb, settingsDb, feishuDb } from '../database/db.js';
6
6
  import { generateToken, authenticateToken, JWT_SECRET } from '../middleware/auth.js';
7
7
  import { initUserDirectories } from '../services/user-directories.js';
8
8
  import { initSystemRepoForUser } from '../services/system-repo.js';
@@ -285,10 +285,24 @@ router.get('/limit-status', authenticateToken, (req, res) => {
285
285
  }
286
286
  });
287
287
 
288
+ // Get current user's Feishu binding status
289
+ router.get('/feishu-status', authenticateToken, (req, res) => {
290
+ try {
291
+ const binding = feishuDb.getBindingByUserUuid(req.user.uuid);
292
+ if (binding) {
293
+ res.json({ bound: true, feishuOpenId: binding.feishu_open_id, createdAt: binding.created_at });
294
+ } else {
295
+ res.json({ bound: false });
296
+ }
297
+ } catch (error) {
298
+ console.error('Error fetching Feishu binding status:', error);
299
+ res.status(500).json({ error: '查询绑定状态失败' });
300
+ }
301
+ });
302
+
288
303
  // Generate a short-lived token for binding a Feishu account
289
304
  // The user opens the web UI, calls this endpoint, then sends /auth <token> in Feishu private chat
290
- router.post('/feishu-bind-token', authenticateToken, (req, res) => {
291
- try {
305
+ router.post('/feishu-bind-token', authenticateToken, (req, res) => { try {
292
306
  const token = jwt.sign(
293
307
  { userId: req.user.id, uuid: req.user.uuid, email: req.user.email },
294
308
  JWT_SECRET,
@@ -2,7 +2,7 @@ import express from 'express';
2
2
  import { promises as fs } from 'fs';
3
3
  import path from 'path';
4
4
  import { spawn } from 'child_process';
5
- import { addProjectManually } from '../projects.js';
5
+ import { addProjectManually, loadProjectConfig } from '../projects.js';
6
6
  import { getUserPaths } from '../services/user-directories.js';
7
7
 
8
8
  const router = express.Router();
@@ -101,6 +101,32 @@ router.post('/create-workspace', async (req, res) => {
101
101
  // Check if directory already exists
102
102
  try {
103
103
  await fs.access(absolutePath);
104
+ // Folder exists - check if it's registered in project config
105
+ const config = await loadProjectConfig(userUuid);
106
+ const projectKey = absolutePath.replace(/\//g, '-');
107
+ if (!config[projectKey] || !config[projectKey].manuallyAdded) {
108
+ // Orphan folder: registered in config without manuallyAdded, or not registered at all
109
+ // Re-register it so the user can find it in the sidebar
110
+ const project = await addProjectManually(absolutePath, null, userUuid).catch(async () => {
111
+ // Already configured (shouldn't happen here, but handle gracefully)
112
+ const existingConfig = await loadProjectConfig(userUuid);
113
+ const existingEntry = existingConfig[projectKey];
114
+ return {
115
+ name: projectKey,
116
+ path: absolutePath,
117
+ fullPath: absolutePath,
118
+ displayName: existingEntry?.displayName || absolutePath.split('/').pop(),
119
+ isManuallyAdded: true,
120
+ sessions: []
121
+ };
122
+ });
123
+ return res.json({
124
+ success: true,
125
+ project,
126
+ recovered: true,
127
+ message: 'Project folder already exists and has been re-registered successfully'
128
+ });
129
+ }
104
130
  return res.status(400).json({
105
131
  error: 'A project with this name already exists. Please choose a different name.'
106
132
  });