@ian2018cs/agenthub 0.1.87 → 0.1.89

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.
@@ -0,0 +1,116 @@
1
+ import express from 'express';
2
+ import { shareDb, userDb } from '../database/db.js';
3
+ import { getSessionMessages, loadProjectConfig } from '../projects.js';
4
+ import { extractShareMessages, saveStaticSnapshot, readStaticSnapshot } from '../services/share-renderer.js';
5
+ import { authenticateToken } from '../middleware/auth.js';
6
+
7
+ const router = express.Router();
8
+
9
+ // GET /api/share/info/:sessionId — 获取当前用户的分享状态(需要鉴权,必须在 /:userUuid/:sessionId 前定义)
10
+ router.get('/info/:sessionId', authenticateToken, (req, res) => {
11
+ const { sessionId } = req.params;
12
+ const userUuid = req.user.uuid;
13
+
14
+ const share = shareDb.getShare(userUuid, sessionId);
15
+ if (!share) {
16
+ return res.status(404).json({ error: 'not found' });
17
+ }
18
+ res.json({
19
+ mode: share.share_mode,
20
+ status: share.status,
21
+ shareUrl: `/share/${userUuid}/${sessionId}`,
22
+ });
23
+ });
24
+
25
+ // GET /api/share/:userUuid/:sessionId — 公开访问,返回分享数据
26
+ router.get('/:userUuid/:sessionId', async (req, res) => {
27
+ const { userUuid, sessionId } = req.params;
28
+
29
+ const share = shareDb.getShare(userUuid, sessionId);
30
+ if (!share || share.status !== 'active') {
31
+ return res.status(404).json({ error: '分享不存在或已关闭' });
32
+ }
33
+
34
+ const user = userDb.getUserByUuid(userUuid);
35
+ const sharerName = user?.email || user?.username || '';
36
+
37
+ const projectConfig = await loadProjectConfig(userUuid);
38
+ const projectDisplayName = projectConfig[share.project_name]?.displayName || share.project_name;
39
+
40
+ if (share.share_mode === 'realtime') {
41
+ try {
42
+ const rawMessages = await getSessionMessages(share.project_name, sessionId, null, 0, userUuid);
43
+ const messages = extractShareMessages(rawMessages);
44
+ return res.json({
45
+ mode: 'realtime',
46
+ title: share.session_title || '对话分享',
47
+ projectName: projectDisplayName,
48
+ messages,
49
+ sharerName,
50
+ });
51
+ } catch (err) {
52
+ console.error('[share] Failed to read realtime messages:', err);
53
+ return res.status(500).json({ error: '读取消息失败' });
54
+ }
55
+ }
56
+
57
+ // 静态模式:读取 snapshot.json
58
+ const snapshot = readStaticSnapshot(userUuid, sessionId);
59
+ if (!snapshot) {
60
+ return res.status(500).json({ error: '读取快照失败' });
61
+ }
62
+ return res.json({
63
+ mode: 'static',
64
+ title: share.session_title || snapshot.title || '对话分享',
65
+ projectName: share.project_name,
66
+ messages: snapshot.messages || [],
67
+ generatedAt: snapshot.generatedAt,
68
+ sharerName,
69
+ });
70
+ });
71
+
72
+ // POST /api/share — 创建或更新分享(需要鉴权)
73
+ router.post('/', authenticateToken, async (req, res) => {
74
+ const { sessionId, projectName, mode, title } = req.body;
75
+ const userUuid = req.user.uuid;
76
+
77
+ if (!sessionId || !projectName || !mode) {
78
+ return res.status(400).json({ error: '缺少必要参数' });
79
+ }
80
+ if (mode !== 'realtime' && mode !== 'static') {
81
+ return res.status(400).json({ error: '无效的分享模式' });
82
+ }
83
+
84
+ if (mode === 'static') {
85
+ try {
86
+ const rawMessages = await getSessionMessages(projectName, sessionId, null, 0, userUuid);
87
+ const messages = extractShareMessages(rawMessages);
88
+ saveStaticSnapshot(messages, userUuid, sessionId, title);
89
+ } catch (err) {
90
+ console.error('[share] Failed to save static snapshot:', err);
91
+ return res.status(500).json({ error: '保存快照失败' });
92
+ }
93
+ }
94
+
95
+ shareDb.upsertShare({
96
+ userUuid,
97
+ sessionId,
98
+ projectName,
99
+ shareMode: mode,
100
+ sessionTitle: title || null,
101
+ });
102
+
103
+ const shareUrl = `/share/${userUuid}/${sessionId}`;
104
+ res.json({ shareUrl, mode });
105
+ });
106
+
107
+ // DELETE /api/share/:sessionId — 停止分享(需要鉴权)
108
+ router.delete('/:sessionId', authenticateToken, (req, res) => {
109
+ const { sessionId } = req.params;
110
+ const userUuid = req.user.uuid;
111
+
112
+ shareDb.stopShare(userUuid, sessionId);
113
+ res.json({ ok: true });
114
+ });
115
+
116
+ export default router;
@@ -178,6 +178,9 @@ registry.register(sdkCronCreateRedirect);
178
178
  registry.register(sdkCronDeleteRedirect);
179
179
  registry.register(sdkCronListRedirect);
180
180
 
181
+ import userInfoTool from './user-info.js';
182
+ registry.register(userInfoTool);
183
+
181
184
  // 重新导出后台任务相关模块,供 server/index.js 使用
182
185
  export {
183
186
  backgroundTaskPool,
@@ -0,0 +1,32 @@
1
+ import { userDb } from '../../database/db.js';
2
+
3
+ export default {
4
+ name: '__get_user_info__',
5
+
6
+ systemPrompt: `
7
+ ## 获取当前用户信息
8
+ 当你需要了解当前登录用户的基本信息时,执行:
9
+ Bash(__get_user_info__)
10
+ 返回当前用户的 username、email、uuid 三个字段(JSON 格式)。无需任何参数。
11
+ `.trim(),
12
+
13
+ match(hookInput) {
14
+ return (
15
+ hookInput.tool_name === 'Bash' &&
16
+ hookInput.tool_input?.command?.trim() === '__get_user_info__'
17
+ );
18
+ },
19
+
20
+ async execute(hookInput, { userUuid }) {
21
+ const user = userDb.getUserByUuid(userUuid);
22
+ if (!user) {
23
+ return { decision: 'deny', reason: '无法获取用户信息:用户不存在' };
24
+ }
25
+ const info = JSON.stringify(
26
+ { username: user.username, email: user.email, uuid: user.uuid },
27
+ null,
28
+ 2
29
+ );
30
+ return { decision: 'deny', reason: info };
31
+ },
32
+ };
@@ -0,0 +1,105 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+
4
+ const DATA_DIR = process.env.DATA_DIR || path.join(process.cwd(), 'data');
5
+ const SHARE_DIR = path.join(DATA_DIR, 'sharepage');
6
+
7
+ const SKIP_USER_PREFIXES = [
8
+ '<command-args>',
9
+ '<local-command-stdout>',
10
+ '<system-reminder>',
11
+ '[Background Task Result]',
12
+ '<task-notification>',
13
+ 'Caveat:',
14
+ 'This session is being continued from a previous',
15
+ '[Request interrupted',
16
+ ];
17
+
18
+ /**
19
+ * 从包含 <command-message> 标签的文本中提取命令消息内容
20
+ * e.g. "<command-message>foo</command-message><command-name>/foo</command-name>" → "foo"
21
+ */
22
+ function extractCommandMessage(text) {
23
+ const m = text.match(/<command-message>([\s\S]*?)<\/command-message>/);
24
+ return m ? m[1].trim() : null;
25
+ }
26
+
27
+ /**
28
+ * 从原始 JSONL 消息数组中提取纯文本消息(过滤工具调用、系统消息等)
29
+ * @param {Array} rawMessages
30
+ * @returns {Array<{role, content, timestamp, uuid}>}
31
+ */
32
+ export function extractShareMessages(rawMessages) {
33
+ const results = [];
34
+
35
+ for (const msg of rawMessages) {
36
+ if (msg.isMeta) continue;
37
+
38
+ const role = msg.message?.role;
39
+ if (role !== 'user' && role !== 'assistant') continue;
40
+
41
+ const content = msg.message?.content;
42
+
43
+ if (role === 'user') {
44
+ let text = '';
45
+ if (typeof content === 'string') {
46
+ text = content;
47
+ } else if (Array.isArray(content)) {
48
+ text = content.filter(p => p.type === 'text').map(p => p.text).join('\n');
49
+ }
50
+ if (!text) continue;
51
+ if (SKIP_USER_PREFIXES.some(prefix => text.startsWith(prefix))) continue;
52
+ if (text.startsWith('<command-message>') || text.startsWith('<command-name>')) {
53
+ const cmd = extractCommandMessage(text);
54
+ if (!cmd) continue;
55
+ text = `/${cmd}`;
56
+ }
57
+
58
+ results.push({ role: 'user', content: text, timestamp: msg.timestamp, uuid: msg.uuid });
59
+
60
+ } else if (role === 'assistant') {
61
+ let text = '';
62
+ if (typeof content === 'string') {
63
+ text = content;
64
+ } else if (Array.isArray(content)) {
65
+ text = content.filter(p => p.type === 'text').map(p => p.text).join('\n');
66
+ }
67
+ if (!text.trim()) continue;
68
+
69
+ results.push({ role: 'assistant', content: text, timestamp: msg.timestamp, uuid: msg.uuid });
70
+ }
71
+ }
72
+
73
+ return results;
74
+ }
75
+
76
+ /**
77
+ * 保存静态快照 JSON(供前端渲染)
78
+ * @returns {string} JSON 文件绝对路径
79
+ */
80
+ export function saveStaticSnapshot(messages, userUuid, sessionId, title) {
81
+ const sessionDir = path.join(SHARE_DIR, userUuid, sessionId);
82
+ fs.mkdirSync(sessionDir, { recursive: true });
83
+
84
+ const jsonPath = path.join(sessionDir, 'snapshot.json');
85
+ fs.writeFileSync(jsonPath, JSON.stringify({
86
+ messages,
87
+ title,
88
+ generatedAt: new Date().toISOString(),
89
+ }, null, 2), 'utf8');
90
+
91
+ return jsonPath;
92
+ }
93
+
94
+ /**
95
+ * 读取静态快照 JSON
96
+ * @returns {{ messages, title, generatedAt } | null}
97
+ */
98
+ export function readStaticSnapshot(userUuid, sessionId) {
99
+ const jsonPath = path.join(SHARE_DIR, userUuid, sessionId, 'snapshot.json');
100
+ try {
101
+ return JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
102
+ } catch {
103
+ return null;
104
+ }
105
+ }