@hasna/conversations 0.1.33 → 0.2.1
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/bin/index.js +895 -13
- package/bin/mcp.js +713 -11
- package/dist/index.d.ts +11 -2
- package/dist/index.js +545 -9
- package/dist/lib/graph.d.ts +58 -0
- package/dist/lib/graph.test.d.ts +1 -0
- package/dist/lib/hot.d.ts +26 -0
- package/dist/lib/hot.test.d.ts +1 -0
- package/dist/lib/messages.d.ts +2 -2
- package/dist/lib/sessions.d.ts +14 -0
- package/dist/lib/summary.d.ts +36 -0
- package/dist/lib/summary.test.d.ts +1 -0
- package/dist/lib/topics.d.ts +31 -0
- package/dist/lib/topics.test.d.ts +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/index-Bw0wMcXE.js +0 -186
- package/dashboard/dist/assets/index-CF_GDtNp.css +0 -1
- package/dashboard/dist/index.html +0 -13
- package/dashboard/dist/logo.jpg +0 -0
package/bin/index.js
CHANGED
|
@@ -2506,12 +2506,18 @@ function getThreadReplies(messageId) {
|
|
|
2506
2506
|
function searchMessages(opts) {
|
|
2507
2507
|
const db2 = getDb();
|
|
2508
2508
|
const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
|
|
2509
|
+
const sortByRelevance = opts.sort !== "recent";
|
|
2510
|
+
const priorityWeights = { urgent: 10, high: 5, normal: 1, low: 0.5 };
|
|
2509
2511
|
try {
|
|
2510
|
-
const ftsConditions = [];
|
|
2511
2512
|
const ftsParams = [];
|
|
2512
|
-
const
|
|
2513
|
-
|
|
2514
|
-
|
|
2513
|
+
const query = opts.query.trim();
|
|
2514
|
+
let ftsQuery;
|
|
2515
|
+
if (query.startsWith('"') && query.endsWith('"')) {
|
|
2516
|
+
ftsQuery = query;
|
|
2517
|
+
} else {
|
|
2518
|
+
const words = query.split(/\s+/).filter(Boolean);
|
|
2519
|
+
ftsQuery = words.map((w) => `"${w.replace(/"/g, '""')}"`).join(" ");
|
|
2520
|
+
}
|
|
2515
2521
|
ftsParams.push(ftsQuery);
|
|
2516
2522
|
let extraWhere = "";
|
|
2517
2523
|
if (opts.space) {
|
|
@@ -2526,11 +2532,23 @@ function searchMessages(opts) {
|
|
|
2526
2532
|
extraWhere += " AND m.to_agent = ?";
|
|
2527
2533
|
ftsParams.push(opts.to);
|
|
2528
2534
|
}
|
|
2529
|
-
const
|
|
2535
|
+
const orderClause = sortByRelevance ? "ORDER BY rank" : "ORDER BY m.created_at DESC, m.id DESC";
|
|
2536
|
+
const rows2 = db2.prepare(`SELECT m.*, rank,
|
|
2537
|
+
snippet(messages_fts, 0, '**', '**', '...', 20) as snippet
|
|
2538
|
+
FROM messages m
|
|
2530
2539
|
JOIN messages_fts ON messages_fts.rowid = m.id
|
|
2531
|
-
WHERE
|
|
2532
|
-
|
|
2533
|
-
|
|
2540
|
+
WHERE messages_fts MATCH ?${extraWhere}
|
|
2541
|
+
${orderClause} LIMIT ${limit}`).all(...ftsParams);
|
|
2542
|
+
const maxRank = rows2.reduce((max, r) => Math.max(max, Math.abs(r.rank || 0)), 0) || 1;
|
|
2543
|
+
return rows2.map((row) => {
|
|
2544
|
+
const msg = parseMessage(row);
|
|
2545
|
+
const ftsScore = maxRank > 0 ? Math.abs(row.rank || 0) / maxRank * 100 : 50;
|
|
2546
|
+
const priorityBoost = priorityWeights[msg.priority] || 1;
|
|
2547
|
+
const pinnedBoost = msg.pinned_at ? 20 : 0;
|
|
2548
|
+
const blockingBoost = msg.blocking ? 15 : 0;
|
|
2549
|
+
const relevance_score = Math.round((ftsScore * priorityBoost + pinnedBoost + blockingBoost) * 100) / 100;
|
|
2550
|
+
return { ...msg, snippet: row.snippet || null, relevance_score };
|
|
2551
|
+
});
|
|
2534
2552
|
} catch {}
|
|
2535
2553
|
const conditions = ["content LIKE ?"];
|
|
2536
2554
|
const params = [`%${opts.query}%`];
|
|
@@ -2548,7 +2566,10 @@ function searchMessages(opts) {
|
|
|
2548
2566
|
}
|
|
2549
2567
|
const where = `WHERE ${conditions.join(" AND ")}`;
|
|
2550
2568
|
const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at DESC, id DESC LIMIT ${limit}`).all(...params);
|
|
2551
|
-
return rows.map(
|
|
2569
|
+
return rows.map((row) => {
|
|
2570
|
+
const msg = parseMessage(row);
|
|
2571
|
+
return { ...msg, snippet: null, relevance_score: 0 };
|
|
2572
|
+
});
|
|
2552
2573
|
}
|
|
2553
2574
|
var init_messages = __esm(() => {
|
|
2554
2575
|
init_db();
|
|
@@ -2584,6 +2605,32 @@ function listSessions(agent) {
|
|
|
2584
2605
|
};
|
|
2585
2606
|
});
|
|
2586
2607
|
}
|
|
2608
|
+
function getSessionActivity(sessionId) {
|
|
2609
|
+
const db2 = getDb();
|
|
2610
|
+
const exists = db2.prepare("SELECT 1 FROM messages WHERE session_id = ? LIMIT 1").get(sessionId);
|
|
2611
|
+
if (!exists)
|
|
2612
|
+
return null;
|
|
2613
|
+
const msgsLast1h = db2.prepare("SELECT COUNT(*) as c FROM messages WHERE session_id = ? AND created_at > strftime('%Y-%m-%dT%H:%M:%f', 'now', '-1 hour')").get(sessionId).c;
|
|
2614
|
+
const msgsLast24h = db2.prepare("SELECT COUNT(*) as c FROM messages WHERE session_id = ? AND created_at > strftime('%Y-%m-%dT%H:%M:%f', 'now', '-24 hours')").get(sessionId).c;
|
|
2615
|
+
const uniqueAgents = db2.prepare("SELECT COUNT(DISTINCT from_agent) as c FROM messages WHERE session_id = ?").get(sessionId).c;
|
|
2616
|
+
const totalMsgs = db2.prepare("SELECT COUNT(*) as c FROM messages WHERE session_id = ?").get(sessionId).c;
|
|
2617
|
+
const replyCount = db2.prepare("SELECT COUNT(*) as c FROM messages WHERE session_id = ? AND reply_to IS NOT NULL").get(sessionId).c;
|
|
2618
|
+
const replyRatio = totalMsgs > 0 ? Math.round(replyCount / totalMsgs * 100) / 100 : 0;
|
|
2619
|
+
const priorityRow = db2.prepare("SELECT priority, COUNT(*) as c FROM messages WHERE session_id = ? GROUP BY priority ORDER BY c DESC LIMIT 1").get(sessionId);
|
|
2620
|
+
const reactionCount = db2.prepare("SELECT COUNT(*) as c FROM reactions r JOIN messages m ON r.message_id = m.id WHERE m.session_id = ?").get(sessionId).c;
|
|
2621
|
+
const agentsLast1h = db2.prepare("SELECT COUNT(DISTINCT from_agent) as c FROM messages WHERE session_id = ? AND created_at > strftime('%Y-%m-%dT%H:%M:%f', 'now', '-1 hour')").get(sessionId).c;
|
|
2622
|
+
const isTrending = msgsLast1h >= 5 || agentsLast1h >= 3;
|
|
2623
|
+
return {
|
|
2624
|
+
session_id: sessionId,
|
|
2625
|
+
msgs_last_1h: msgsLast1h,
|
|
2626
|
+
msgs_last_24h: msgsLast24h,
|
|
2627
|
+
unique_agents: uniqueAgents,
|
|
2628
|
+
reply_ratio: replyRatio,
|
|
2629
|
+
avg_priority: priorityRow?.priority ?? "normal",
|
|
2630
|
+
reaction_count: reactionCount,
|
|
2631
|
+
is_trending: isTrending
|
|
2632
|
+
};
|
|
2633
|
+
}
|
|
2587
2634
|
var init_sessions = __esm(() => {
|
|
2588
2635
|
init_db();
|
|
2589
2636
|
});
|
|
@@ -3525,6 +3572,497 @@ var init_reactions = __esm(() => {
|
|
|
3525
3572
|
init_db();
|
|
3526
3573
|
});
|
|
3527
3574
|
|
|
3575
|
+
// src/lib/hot.ts
|
|
3576
|
+
function computeHotness(sessionId) {
|
|
3577
|
+
const db2 = getDb();
|
|
3578
|
+
const base = db2.prepare(`
|
|
3579
|
+
SELECT session_id,
|
|
3580
|
+
GROUP_CONCAT(DISTINCT from_agent) as agents,
|
|
3581
|
+
MAX(space) as space,
|
|
3582
|
+
MAX(created_at) as last_message_at,
|
|
3583
|
+
COUNT(*) as message_count
|
|
3584
|
+
FROM messages WHERE session_id = ?
|
|
3585
|
+
GROUP BY session_id
|
|
3586
|
+
`).get(sessionId);
|
|
3587
|
+
if (!base)
|
|
3588
|
+
return null;
|
|
3589
|
+
const msgsLast1h = db2.prepare("SELECT COUNT(*) as c FROM messages WHERE session_id = ? AND created_at > strftime('%Y-%m-%dT%H:%M:%f', 'now', '-1 hour')").get(sessionId).c;
|
|
3590
|
+
const msgsLast24h = db2.prepare("SELECT COUNT(*) as c FROM messages WHERE session_id = ? AND created_at > strftime('%Y-%m-%dT%H:%M:%f', 'now', '-24 hours')").get(sessionId).c;
|
|
3591
|
+
const uniqueAgents = db2.prepare("SELECT COUNT(DISTINCT from_agent) as c FROM messages WHERE session_id = ?").get(sessionId).c;
|
|
3592
|
+
const reactionCount = db2.prepare("SELECT COUNT(*) as c FROM reactions r JOIN messages m ON r.message_id = m.id WHERE m.session_id = ?").get(sessionId).c;
|
|
3593
|
+
const replyCount = db2.prepare("SELECT COUNT(*) as c FROM messages WHERE session_id = ? AND reply_to IS NOT NULL").get(sessionId).c;
|
|
3594
|
+
const highPriorityCount = db2.prepare("SELECT COUNT(*) as c FROM messages WHERE session_id = ? AND priority IN ('high', 'urgent')").get(sessionId).c;
|
|
3595
|
+
const blockerCount = db2.prepare("SELECT COUNT(*) as c FROM messages WHERE session_id = ? AND blocking = 1").get(sessionId).c;
|
|
3596
|
+
const lastMsgMs = new Date(base.last_message_at + "Z").getTime();
|
|
3597
|
+
const hoursSinceLast = Math.max(0, (Date.now() - lastMsgMs) / 3600000);
|
|
3598
|
+
const hotness_score = Math.round(msgsLast1h * 3 + uniqueAgents * 5 + reactionCount * 2 + replyCount * 4 + highPriorityCount * 10 + blockerCount * 20 - hoursSinceLast * 2);
|
|
3599
|
+
return {
|
|
3600
|
+
session_id: base.session_id,
|
|
3601
|
+
participants: base.agents.split(","),
|
|
3602
|
+
space: base.space,
|
|
3603
|
+
last_message_at: base.last_message_at,
|
|
3604
|
+
message_count: base.message_count,
|
|
3605
|
+
hotness_score,
|
|
3606
|
+
metrics: {
|
|
3607
|
+
msgs_last_1h: msgsLast1h,
|
|
3608
|
+
msgs_last_24h: msgsLast24h,
|
|
3609
|
+
unique_agents: uniqueAgents,
|
|
3610
|
+
reaction_count: reactionCount,
|
|
3611
|
+
reply_count: replyCount,
|
|
3612
|
+
high_priority_count: highPriorityCount,
|
|
3613
|
+
blocker_count: blockerCount,
|
|
3614
|
+
hours_since_last: Math.round(hoursSinceLast * 10) / 10
|
|
3615
|
+
}
|
|
3616
|
+
};
|
|
3617
|
+
}
|
|
3618
|
+
function listHotSessions(opts) {
|
|
3619
|
+
const db2 = getDb();
|
|
3620
|
+
const limit = opts?.limit ?? 20;
|
|
3621
|
+
const minScore = opts?.min_score ?? 0;
|
|
3622
|
+
let where = "";
|
|
3623
|
+
const params = [];
|
|
3624
|
+
if (opts?.space) {
|
|
3625
|
+
where = " WHERE space = ?";
|
|
3626
|
+
params.push(opts.space);
|
|
3627
|
+
} else if (opts?.project_id) {
|
|
3628
|
+
where = " WHERE project_id = ?";
|
|
3629
|
+
params.push(opts.project_id);
|
|
3630
|
+
}
|
|
3631
|
+
const sessions = db2.prepare(`SELECT session_id, MAX(created_at) as last_at FROM messages${where} GROUP BY session_id ORDER BY last_at DESC LIMIT 100`).all(...params);
|
|
3632
|
+
const hotSessions = [];
|
|
3633
|
+
for (const { session_id } of sessions) {
|
|
3634
|
+
const hot = computeHotness(session_id);
|
|
3635
|
+
if (hot && hot.hotness_score >= minScore) {
|
|
3636
|
+
hotSessions.push(hot);
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
hotSessions.sort((a, b) => b.hotness_score - a.hotness_score);
|
|
3640
|
+
return hotSessions.slice(0, limit);
|
|
3641
|
+
}
|
|
3642
|
+
var init_hot = __esm(() => {
|
|
3643
|
+
init_db();
|
|
3644
|
+
});
|
|
3645
|
+
|
|
3646
|
+
// src/lib/topics.ts
|
|
3647
|
+
function extractTopics(text, topN = 10) {
|
|
3648
|
+
const cleaned = text.replace(/```[\s\S]*?```/g, " ").replace(/`[^`]+`/g, " ").replace(/https?:\/\/\S+/g, " ").replace(/[#*_~|>\[\](){}]/g, " ").replace(/\d+/g, " ").toLowerCase();
|
|
3649
|
+
const words = cleaned.split(/\s+/).filter((w) => w.length >= 3 && !STOPWORDS.has(w) && /^[a-z]/.test(w));
|
|
3650
|
+
const freq = new Map;
|
|
3651
|
+
for (const w of words) {
|
|
3652
|
+
freq.set(w, (freq.get(w) || 0) + 1);
|
|
3653
|
+
}
|
|
3654
|
+
const totalWords = words.length || 1;
|
|
3655
|
+
return [...freq.entries()].sort((a, b) => b[1] - a[1]).slice(0, topN).map(([topic, count]) => ({
|
|
3656
|
+
topic,
|
|
3657
|
+
weight: Math.round(count / totalWords * 1000) / 1000,
|
|
3658
|
+
count
|
|
3659
|
+
}));
|
|
3660
|
+
}
|
|
3661
|
+
function getSpaceTopics(spaceName, opts) {
|
|
3662
|
+
const db2 = getDb();
|
|
3663
|
+
const limit = opts?.limit ?? 100;
|
|
3664
|
+
const sinceClause = opts?.since ? "AND created_at > ?" : "";
|
|
3665
|
+
const params = [spaceName];
|
|
3666
|
+
if (opts?.since)
|
|
3667
|
+
params.push(opts.since);
|
|
3668
|
+
const rows = db2.prepare(`SELECT content FROM messages WHERE space = ? ${sinceClause} ORDER BY created_at DESC LIMIT ${limit}`).all(...params);
|
|
3669
|
+
const combined = rows.map((r) => r.content).join(`
|
|
3670
|
+
`);
|
|
3671
|
+
return extractTopics(combined, 15);
|
|
3672
|
+
}
|
|
3673
|
+
function getSessionTopics(sessionId, opts) {
|
|
3674
|
+
const db2 = getDb();
|
|
3675
|
+
const limit = opts?.limit ?? 100;
|
|
3676
|
+
const rows = db2.prepare(`SELECT content FROM messages WHERE session_id = ? ORDER BY created_at DESC LIMIT ${limit}`).all(sessionId);
|
|
3677
|
+
const combined = rows.map((r) => r.content).join(`
|
|
3678
|
+
`);
|
|
3679
|
+
return extractTopics(combined, 15);
|
|
3680
|
+
}
|
|
3681
|
+
function getTrendingTopics(opts) {
|
|
3682
|
+
const db2 = getDb();
|
|
3683
|
+
const hours = opts?.hours ?? 24;
|
|
3684
|
+
const topN = opts?.top_n ?? 20;
|
|
3685
|
+
let where = `WHERE created_at > strftime('%Y-%m-%dT%H:%M:%f', 'now', '-${hours} hours')`;
|
|
3686
|
+
const params = [];
|
|
3687
|
+
if (opts?.project_id) {
|
|
3688
|
+
where += " AND project_id = ?";
|
|
3689
|
+
params.push(opts.project_id);
|
|
3690
|
+
}
|
|
3691
|
+
const rows = db2.prepare(`SELECT content FROM messages ${where} ORDER BY created_at DESC LIMIT 500`).all(...params);
|
|
3692
|
+
const combined = rows.map((r) => r.content).join(`
|
|
3693
|
+
`);
|
|
3694
|
+
return extractTopics(combined, topN);
|
|
3695
|
+
}
|
|
3696
|
+
var STOPWORDS;
|
|
3697
|
+
var init_topics = __esm(() => {
|
|
3698
|
+
init_db();
|
|
3699
|
+
STOPWORDS = new Set([
|
|
3700
|
+
"a",
|
|
3701
|
+
"an",
|
|
3702
|
+
"the",
|
|
3703
|
+
"and",
|
|
3704
|
+
"or",
|
|
3705
|
+
"but",
|
|
3706
|
+
"in",
|
|
3707
|
+
"on",
|
|
3708
|
+
"at",
|
|
3709
|
+
"to",
|
|
3710
|
+
"for",
|
|
3711
|
+
"of",
|
|
3712
|
+
"with",
|
|
3713
|
+
"by",
|
|
3714
|
+
"from",
|
|
3715
|
+
"is",
|
|
3716
|
+
"it",
|
|
3717
|
+
"this",
|
|
3718
|
+
"that",
|
|
3719
|
+
"are",
|
|
3720
|
+
"was",
|
|
3721
|
+
"were",
|
|
3722
|
+
"be",
|
|
3723
|
+
"been",
|
|
3724
|
+
"being",
|
|
3725
|
+
"have",
|
|
3726
|
+
"has",
|
|
3727
|
+
"had",
|
|
3728
|
+
"do",
|
|
3729
|
+
"does",
|
|
3730
|
+
"did",
|
|
3731
|
+
"will",
|
|
3732
|
+
"would",
|
|
3733
|
+
"could",
|
|
3734
|
+
"should",
|
|
3735
|
+
"may",
|
|
3736
|
+
"might",
|
|
3737
|
+
"shall",
|
|
3738
|
+
"can",
|
|
3739
|
+
"need",
|
|
3740
|
+
"not",
|
|
3741
|
+
"no",
|
|
3742
|
+
"so",
|
|
3743
|
+
"if",
|
|
3744
|
+
"then",
|
|
3745
|
+
"than",
|
|
3746
|
+
"too",
|
|
3747
|
+
"very",
|
|
3748
|
+
"just",
|
|
3749
|
+
"about",
|
|
3750
|
+
"up",
|
|
3751
|
+
"out",
|
|
3752
|
+
"all",
|
|
3753
|
+
"also",
|
|
3754
|
+
"as",
|
|
3755
|
+
"into",
|
|
3756
|
+
"only",
|
|
3757
|
+
"other",
|
|
3758
|
+
"each",
|
|
3759
|
+
"every",
|
|
3760
|
+
"both",
|
|
3761
|
+
"few",
|
|
3762
|
+
"more",
|
|
3763
|
+
"most",
|
|
3764
|
+
"some",
|
|
3765
|
+
"such",
|
|
3766
|
+
"any",
|
|
3767
|
+
"over",
|
|
3768
|
+
"after",
|
|
3769
|
+
"before",
|
|
3770
|
+
"between",
|
|
3771
|
+
"under",
|
|
3772
|
+
"above",
|
|
3773
|
+
"here",
|
|
3774
|
+
"there",
|
|
3775
|
+
"when",
|
|
3776
|
+
"where",
|
|
3777
|
+
"how",
|
|
3778
|
+
"what",
|
|
3779
|
+
"which",
|
|
3780
|
+
"who",
|
|
3781
|
+
"whom",
|
|
3782
|
+
"why",
|
|
3783
|
+
"its",
|
|
3784
|
+
"my",
|
|
3785
|
+
"your",
|
|
3786
|
+
"his",
|
|
3787
|
+
"her",
|
|
3788
|
+
"our",
|
|
3789
|
+
"their",
|
|
3790
|
+
"we",
|
|
3791
|
+
"you",
|
|
3792
|
+
"he",
|
|
3793
|
+
"she",
|
|
3794
|
+
"they",
|
|
3795
|
+
"i",
|
|
3796
|
+
"me",
|
|
3797
|
+
"him",
|
|
3798
|
+
"us",
|
|
3799
|
+
"them",
|
|
3800
|
+
"now",
|
|
3801
|
+
"new",
|
|
3802
|
+
"get",
|
|
3803
|
+
"got",
|
|
3804
|
+
"go",
|
|
3805
|
+
"going",
|
|
3806
|
+
"done",
|
|
3807
|
+
"make",
|
|
3808
|
+
"made",
|
|
3809
|
+
"see",
|
|
3810
|
+
"know",
|
|
3811
|
+
"think",
|
|
3812
|
+
"want",
|
|
3813
|
+
"one",
|
|
3814
|
+
"two",
|
|
3815
|
+
"like",
|
|
3816
|
+
"still",
|
|
3817
|
+
"back",
|
|
3818
|
+
"even"
|
|
3819
|
+
]);
|
|
3820
|
+
});
|
|
3821
|
+
|
|
3822
|
+
// src/lib/summary.ts
|
|
3823
|
+
function getConversationSummary(sessionOrSpace, opts) {
|
|
3824
|
+
const db2 = getDb();
|
|
3825
|
+
const limit = opts?.limit ?? 50;
|
|
3826
|
+
const isSpace = sessionOrSpace.startsWith("space:") || db2.prepare("SELECT 1 FROM spaces WHERE name = ?").get(sessionOrSpace);
|
|
3827
|
+
const filterCol = isSpace ? "space" : "session_id";
|
|
3828
|
+
const filterVal = isSpace && !sessionOrSpace.startsWith("space:") ? sessionOrSpace : sessionOrSpace;
|
|
3829
|
+
const messages = db2.prepare(`SELECT * FROM messages WHERE ${filterCol} = ? ORDER BY created_at DESC LIMIT ${limit}`).all(filterVal);
|
|
3830
|
+
if (messages.length === 0)
|
|
3831
|
+
return null;
|
|
3832
|
+
const agents = new Set;
|
|
3833
|
+
for (const m of messages) {
|
|
3834
|
+
agents.add(m.from_agent);
|
|
3835
|
+
if (m.to_agent)
|
|
3836
|
+
agents.add(m.to_agent);
|
|
3837
|
+
}
|
|
3838
|
+
const dates = messages.map((m) => m.created_at).sort();
|
|
3839
|
+
const dateRange = { first: dates[0], last: dates[dates.length - 1] };
|
|
3840
|
+
const allContent = messages.map((m) => m.content).join(`
|
|
3841
|
+
`);
|
|
3842
|
+
const topics = extractTopics(allContent, 10);
|
|
3843
|
+
const keyMessages = [];
|
|
3844
|
+
for (const m of messages) {
|
|
3845
|
+
const priority = m.priority;
|
|
3846
|
+
if (priority === "high" || priority === "urgent") {
|
|
3847
|
+
keyMessages.push({
|
|
3848
|
+
id: m.id,
|
|
3849
|
+
from: m.from_agent,
|
|
3850
|
+
content: m.content.slice(0, 200),
|
|
3851
|
+
reason: `${priority} priority`
|
|
3852
|
+
});
|
|
3853
|
+
}
|
|
3854
|
+
if (m.blocking) {
|
|
3855
|
+
keyMessages.push({
|
|
3856
|
+
id: m.id,
|
|
3857
|
+
from: m.from_agent,
|
|
3858
|
+
content: m.content.slice(0, 200),
|
|
3859
|
+
reason: "blocking message"
|
|
3860
|
+
});
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3863
|
+
for (const m of messages) {
|
|
3864
|
+
if (m.pinned_at) {
|
|
3865
|
+
keyMessages.push({
|
|
3866
|
+
id: m.id,
|
|
3867
|
+
from: m.from_agent,
|
|
3868
|
+
content: m.content.slice(0, 200),
|
|
3869
|
+
reason: "pinned"
|
|
3870
|
+
});
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
const msgIds = messages.map((m) => m.id);
|
|
3874
|
+
if (msgIds.length > 0) {
|
|
3875
|
+
const placeholders = msgIds.map(() => "?").join(",");
|
|
3876
|
+
const reacted = db2.prepare(`SELECT message_id, COUNT(*) as c FROM reactions WHERE message_id IN (${placeholders}) GROUP BY message_id ORDER BY c DESC LIMIT 3`).all(...msgIds);
|
|
3877
|
+
for (const r of reacted) {
|
|
3878
|
+
const m = messages.find((msg) => msg.id === r.message_id);
|
|
3879
|
+
if (m) {
|
|
3880
|
+
keyMessages.push({
|
|
3881
|
+
id: r.message_id,
|
|
3882
|
+
from: m.from_agent,
|
|
3883
|
+
content: m.content.slice(0, 200),
|
|
3884
|
+
reason: `${r.c} reaction(s)`
|
|
3885
|
+
});
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
const seen = new Set;
|
|
3890
|
+
const uniqueKey = keyMessages.filter((k) => {
|
|
3891
|
+
if (seen.has(k.id))
|
|
3892
|
+
return false;
|
|
3893
|
+
seen.add(k.id);
|
|
3894
|
+
return true;
|
|
3895
|
+
}).slice(0, 10);
|
|
3896
|
+
const blockers = messages.filter((m) => m.blocking && !m.read_at).map((m) => ({
|
|
3897
|
+
id: m.id,
|
|
3898
|
+
from: m.from_agent,
|
|
3899
|
+
content: m.content.slice(0, 200),
|
|
3900
|
+
created_at: m.created_at
|
|
3901
|
+
}));
|
|
3902
|
+
const replyCount = messages.filter((m) => m.reply_to).length;
|
|
3903
|
+
const reactionCount = msgIds.length > 0 ? db2.prepare(`SELECT COUNT(*) as c FROM reactions WHERE message_id IN (${msgIds.map(() => "?").join(",")})`).get(...msgIds).c : 0;
|
|
3904
|
+
const priorityCounts = {};
|
|
3905
|
+
for (const m of messages) {
|
|
3906
|
+
const p = m.priority;
|
|
3907
|
+
priorityCounts[p] = (priorityCounts[p] || 0) + 1;
|
|
3908
|
+
}
|
|
3909
|
+
const avgPriority = Object.entries(priorityCounts).sort((a, b) => b[1] - a[1])[0]?.[0] ?? "normal";
|
|
3910
|
+
return {
|
|
3911
|
+
session_id: sessionOrSpace,
|
|
3912
|
+
participants: [...agents].filter((a) => a !== sessionOrSpace),
|
|
3913
|
+
message_count: messages.length,
|
|
3914
|
+
date_range: dateRange,
|
|
3915
|
+
topics,
|
|
3916
|
+
key_messages: uniqueKey,
|
|
3917
|
+
unresolved_blockers: blockers,
|
|
3918
|
+
activity: {
|
|
3919
|
+
reply_count: replyCount,
|
|
3920
|
+
reaction_count: reactionCount,
|
|
3921
|
+
avg_priority: avgPriority
|
|
3922
|
+
}
|
|
3923
|
+
};
|
|
3924
|
+
}
|
|
3925
|
+
var init_summary = __esm(() => {
|
|
3926
|
+
init_db();
|
|
3927
|
+
init_topics();
|
|
3928
|
+
});
|
|
3929
|
+
|
|
3930
|
+
// src/lib/graph.ts
|
|
3931
|
+
function ensureGraphTable() {
|
|
3932
|
+
const db2 = getDb();
|
|
3933
|
+
db2.exec(`
|
|
3934
|
+
CREATE TABLE IF NOT EXISTS graph_edges (
|
|
3935
|
+
from_type TEXT NOT NULL,
|
|
3936
|
+
from_id TEXT NOT NULL,
|
|
3937
|
+
to_type TEXT NOT NULL,
|
|
3938
|
+
to_id TEXT NOT NULL,
|
|
3939
|
+
relation TEXT NOT NULL,
|
|
3940
|
+
weight REAL NOT NULL DEFAULT 1,
|
|
3941
|
+
metadata TEXT,
|
|
3942
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
3943
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
3944
|
+
UNIQUE(from_type, from_id, to_type, to_id, relation)
|
|
3945
|
+
)
|
|
3946
|
+
`);
|
|
3947
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_graph_from ON graph_edges(from_type, from_id)");
|
|
3948
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_graph_to ON graph_edges(to_type, to_id)");
|
|
3949
|
+
db2.exec("CREATE INDEX IF NOT EXISTS idx_graph_relation ON graph_edges(relation)");
|
|
3950
|
+
}
|
|
3951
|
+
function buildGraph() {
|
|
3952
|
+
const db2 = getDb();
|
|
3953
|
+
ensureGraphTable();
|
|
3954
|
+
let created = 0;
|
|
3955
|
+
let updated = 0;
|
|
3956
|
+
const upsert = db2.prepare(`
|
|
3957
|
+
INSERT INTO graph_edges (from_type, from_id, to_type, to_id, relation, weight, updated_at)
|
|
3958
|
+
VALUES (?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'))
|
|
3959
|
+
ON CONFLICT(from_type, from_id, to_type, to_id, relation) DO UPDATE SET
|
|
3960
|
+
weight = excluded.weight,
|
|
3961
|
+
updated_at = excluded.updated_at
|
|
3962
|
+
`);
|
|
3963
|
+
const insertOrUpdate = db2.transaction(() => {
|
|
3964
|
+
const dmPairs = db2.prepare(`
|
|
3965
|
+
SELECT from_agent, to_agent, COUNT(*) as cnt, MAX(created_at) as last_at
|
|
3966
|
+
FROM messages WHERE space IS NULL AND from_agent != to_agent
|
|
3967
|
+
GROUP BY from_agent, to_agent
|
|
3968
|
+
`).all();
|
|
3969
|
+
for (const pair of dmPairs) {
|
|
3970
|
+
const existing = db2.prepare("SELECT 1 FROM graph_edges WHERE from_type='agent' AND from_id=? AND to_type='agent' AND to_id=? AND relation='communicates_with'").get(pair.from_agent, pair.to_agent);
|
|
3971
|
+
upsert.run("agent", pair.from_agent, "agent", pair.to_agent, "communicates_with", pair.cnt);
|
|
3972
|
+
if (existing)
|
|
3973
|
+
updated++;
|
|
3974
|
+
else
|
|
3975
|
+
created++;
|
|
3976
|
+
}
|
|
3977
|
+
const spacePosts = db2.prepare(`
|
|
3978
|
+
SELECT from_agent, space, COUNT(*) as cnt
|
|
3979
|
+
FROM messages WHERE space IS NOT NULL
|
|
3980
|
+
GROUP BY from_agent, space
|
|
3981
|
+
`).all();
|
|
3982
|
+
for (const sp of spacePosts) {
|
|
3983
|
+
const existing = db2.prepare("SELECT 1 FROM graph_edges WHERE from_type='agent' AND from_id=? AND to_type='space' AND to_id=? AND relation='posts_in'").get(sp.from_agent, sp.space);
|
|
3984
|
+
upsert.run("agent", sp.from_agent, "space", sp.space, "posts_in", sp.cnt);
|
|
3985
|
+
if (existing)
|
|
3986
|
+
updated++;
|
|
3987
|
+
else
|
|
3988
|
+
created++;
|
|
3989
|
+
}
|
|
3990
|
+
const members = db2.prepare("SELECT agent, space FROM space_members").all();
|
|
3991
|
+
for (const m of members) {
|
|
3992
|
+
const existing = db2.prepare("SELECT 1 FROM graph_edges WHERE from_type='agent' AND from_id=? AND to_type='space' AND to_id=? AND relation='member_of'").get(m.agent, m.space);
|
|
3993
|
+
upsert.run("agent", m.agent, "space", m.space, "member_of", 1);
|
|
3994
|
+
if (existing)
|
|
3995
|
+
updated++;
|
|
3996
|
+
else
|
|
3997
|
+
created++;
|
|
3998
|
+
}
|
|
3999
|
+
const spaceProjects = db2.prepare("SELECT name, project_id FROM spaces WHERE project_id IS NOT NULL").all();
|
|
4000
|
+
for (const sp of spaceProjects) {
|
|
4001
|
+
const existing = db2.prepare("SELECT 1 FROM graph_edges WHERE from_type='space' AND from_id=? AND to_type='project' AND to_id=? AND relation='belongs_to'").get(sp.name, sp.project_id);
|
|
4002
|
+
upsert.run("space", sp.name, "project", sp.project_id, "belongs_to", 1);
|
|
4003
|
+
if (existing)
|
|
4004
|
+
updated++;
|
|
4005
|
+
else
|
|
4006
|
+
created++;
|
|
4007
|
+
}
|
|
4008
|
+
});
|
|
4009
|
+
insertOrUpdate();
|
|
4010
|
+
return { edges_created: created, edges_updated: updated };
|
|
4011
|
+
}
|
|
4012
|
+
function getRelated(entityType, entityId) {
|
|
4013
|
+
const db2 = getDb();
|
|
4014
|
+
ensureGraphTable();
|
|
4015
|
+
const outgoing = db2.prepare(`
|
|
4016
|
+
SELECT to_type as type, to_id as id, relation, weight FROM graph_edges
|
|
4017
|
+
WHERE from_type = ? AND from_id = ? ORDER BY weight DESC
|
|
4018
|
+
`).all(entityType, entityId);
|
|
4019
|
+
const incoming = db2.prepare(`
|
|
4020
|
+
SELECT from_type as type, from_id as id, relation, weight FROM graph_edges
|
|
4021
|
+
WHERE to_type = ? AND to_id = ? ORDER BY weight DESC
|
|
4022
|
+
`).all(entityType, entityId);
|
|
4023
|
+
return [...outgoing, ...incoming];
|
|
4024
|
+
}
|
|
4025
|
+
function getAgentNetwork(agent) {
|
|
4026
|
+
const db2 = getDb();
|
|
4027
|
+
ensureGraphTable();
|
|
4028
|
+
const comms = db2.prepare(`
|
|
4029
|
+
SELECT to_id as agent, weight as message_count,
|
|
4030
|
+
(SELECT MAX(created_at) FROM messages WHERE from_agent = ? AND to_agent = ge.to_id AND space IS NULL) as last_at
|
|
4031
|
+
FROM graph_edges ge
|
|
4032
|
+
WHERE from_type = 'agent' AND from_id = ? AND relation = 'communicates_with'
|
|
4033
|
+
ORDER BY weight DESC LIMIT 20
|
|
4034
|
+
`).all(agent, agent);
|
|
4035
|
+
const spaces = db2.prepare(`
|
|
4036
|
+
SELECT to_id as space, weight as message_count FROM graph_edges
|
|
4037
|
+
WHERE from_type = 'agent' AND from_id = ? AND relation = 'posts_in'
|
|
4038
|
+
ORDER BY weight DESC LIMIT 20
|
|
4039
|
+
`).all(agent);
|
|
4040
|
+
const projects = db2.prepare(`
|
|
4041
|
+
SELECT DISTINCT g2.to_id FROM graph_edges g1
|
|
4042
|
+
JOIN graph_edges g2 ON g1.to_type = 'space' AND g1.to_id = g2.from_id AND g2.relation = 'belongs_to'
|
|
4043
|
+
WHERE g1.from_type = 'agent' AND g1.from_id = ? AND g1.relation IN ('member_of', 'posts_in')
|
|
4044
|
+
`).all(agent);
|
|
4045
|
+
return {
|
|
4046
|
+
agent,
|
|
4047
|
+
communicates_with: comms,
|
|
4048
|
+
spaces,
|
|
4049
|
+
projects: projects.map((p) => p.to_id)
|
|
4050
|
+
};
|
|
4051
|
+
}
|
|
4052
|
+
function getGraphStats() {
|
|
4053
|
+
const db2 = getDb();
|
|
4054
|
+
ensureGraphTable();
|
|
4055
|
+
const total = db2.prepare("SELECT COUNT(*) as c FROM graph_edges").get().c;
|
|
4056
|
+
const byRelation = db2.prepare("SELECT relation, COUNT(*) as c FROM graph_edges GROUP BY relation ORDER BY c DESC").all();
|
|
4057
|
+
const map = {};
|
|
4058
|
+
for (const r of byRelation)
|
|
4059
|
+
map[r.relation] = r.c;
|
|
4060
|
+
return { total_edges: total, by_relation: map };
|
|
4061
|
+
}
|
|
4062
|
+
var init_graph = __esm(() => {
|
|
4063
|
+
init_db();
|
|
4064
|
+
});
|
|
4065
|
+
|
|
3528
4066
|
// src/lib/terminal-markdown.ts
|
|
3529
4067
|
var exports_terminal_markdown = {};
|
|
3530
4068
|
__export(exports_terminal_markdown, {
|
|
@@ -3669,7 +4207,7 @@ var init_poll = __esm(() => {
|
|
|
3669
4207
|
var require_package = __commonJS((exports, module) => {
|
|
3670
4208
|
module.exports = {
|
|
3671
4209
|
name: "@hasna/conversations",
|
|
3672
|
-
version: "0.1
|
|
4210
|
+
version: "0.2.1",
|
|
3673
4211
|
description: "Real-time CLI messaging for AI agents",
|
|
3674
4212
|
type: "module",
|
|
3675
4213
|
bin: {
|
|
@@ -31037,7 +31575,7 @@ var require_formats = __commonJS((exports) => {
|
|
|
31037
31575
|
}
|
|
31038
31576
|
var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
|
|
31039
31577
|
function getTime(strictTimeZone) {
|
|
31040
|
-
return function
|
|
31578
|
+
return function time3(str) {
|
|
31041
31579
|
const matches = TIME.exec(str);
|
|
31042
31580
|
if (!matches)
|
|
31043
31581
|
return false;
|
|
@@ -31301,6 +31839,62 @@ class ExperimentalServerTasks {
|
|
|
31301
31839
|
requestStream(request, resultSchema, options) {
|
|
31302
31840
|
return this._server.requestStream(request, resultSchema, options);
|
|
31303
31841
|
}
|
|
31842
|
+
createMessageStream(params, options) {
|
|
31843
|
+
const clientCapabilities = this._server.getClientCapabilities();
|
|
31844
|
+
if ((params.tools || params.toolChoice) && !clientCapabilities?.sampling?.tools) {
|
|
31845
|
+
throw new Error("Client does not support sampling tools capability.");
|
|
31846
|
+
}
|
|
31847
|
+
if (params.messages.length > 0) {
|
|
31848
|
+
const lastMessage = params.messages[params.messages.length - 1];
|
|
31849
|
+
const lastContent = Array.isArray(lastMessage.content) ? lastMessage.content : [lastMessage.content];
|
|
31850
|
+
const hasToolResults = lastContent.some((c) => c.type === "tool_result");
|
|
31851
|
+
const previousMessage = params.messages.length > 1 ? params.messages[params.messages.length - 2] : undefined;
|
|
31852
|
+
const previousContent = previousMessage ? Array.isArray(previousMessage.content) ? previousMessage.content : [previousMessage.content] : [];
|
|
31853
|
+
const hasPreviousToolUse = previousContent.some((c) => c.type === "tool_use");
|
|
31854
|
+
if (hasToolResults) {
|
|
31855
|
+
if (lastContent.some((c) => c.type !== "tool_result")) {
|
|
31856
|
+
throw new Error("The last message must contain only tool_result content if any is present");
|
|
31857
|
+
}
|
|
31858
|
+
if (!hasPreviousToolUse) {
|
|
31859
|
+
throw new Error("tool_result blocks are not matching any tool_use from the previous message");
|
|
31860
|
+
}
|
|
31861
|
+
}
|
|
31862
|
+
if (hasPreviousToolUse) {
|
|
31863
|
+
const toolUseIds = new Set(previousContent.filter((c) => c.type === "tool_use").map((c) => c.id));
|
|
31864
|
+
const toolResultIds = new Set(lastContent.filter((c) => c.type === "tool_result").map((c) => c.toolUseId));
|
|
31865
|
+
if (toolUseIds.size !== toolResultIds.size || ![...toolUseIds].every((id) => toolResultIds.has(id))) {
|
|
31866
|
+
throw new Error("ids of tool_result blocks and tool_use blocks from previous message do not match");
|
|
31867
|
+
}
|
|
31868
|
+
}
|
|
31869
|
+
}
|
|
31870
|
+
return this.requestStream({
|
|
31871
|
+
method: "sampling/createMessage",
|
|
31872
|
+
params
|
|
31873
|
+
}, CreateMessageResultSchema, options);
|
|
31874
|
+
}
|
|
31875
|
+
elicitInputStream(params, options) {
|
|
31876
|
+
const clientCapabilities = this._server.getClientCapabilities();
|
|
31877
|
+
const mode = params.mode ?? "form";
|
|
31878
|
+
switch (mode) {
|
|
31879
|
+
case "url": {
|
|
31880
|
+
if (!clientCapabilities?.elicitation?.url) {
|
|
31881
|
+
throw new Error("Client does not support url elicitation.");
|
|
31882
|
+
}
|
|
31883
|
+
break;
|
|
31884
|
+
}
|
|
31885
|
+
case "form": {
|
|
31886
|
+
if (!clientCapabilities?.elicitation?.form) {
|
|
31887
|
+
throw new Error("Client does not support form elicitation.");
|
|
31888
|
+
}
|
|
31889
|
+
break;
|
|
31890
|
+
}
|
|
31891
|
+
}
|
|
31892
|
+
const normalizedParams = mode === "form" && params.mode === undefined ? { ...params, mode: "form" } : params;
|
|
31893
|
+
return this.requestStream({
|
|
31894
|
+
method: "elicitation/create",
|
|
31895
|
+
params: normalizedParams
|
|
31896
|
+
}, ElicitResultSchema, options);
|
|
31897
|
+
}
|
|
31304
31898
|
async getTask(taskId, options) {
|
|
31305
31899
|
return this._server.getTask({ taskId }, options);
|
|
31306
31900
|
}
|
|
@@ -31314,6 +31908,9 @@ class ExperimentalServerTasks {
|
|
|
31314
31908
|
return this._server.cancelTask({ taskId }, options);
|
|
31315
31909
|
}
|
|
31316
31910
|
}
|
|
31911
|
+
var init_server = __esm(() => {
|
|
31912
|
+
init_types2();
|
|
31913
|
+
});
|
|
31317
31914
|
|
|
31318
31915
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
|
|
31319
31916
|
function assertToolsCallTaskCapability(requests, method, entityName) {
|
|
@@ -31352,11 +31949,12 @@ function assertClientRequestTaskCapability(requests, method, entityName) {
|
|
|
31352
31949
|
|
|
31353
31950
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
|
|
31354
31951
|
var Server;
|
|
31355
|
-
var
|
|
31952
|
+
var init_server2 = __esm(() => {
|
|
31356
31953
|
init_protocol();
|
|
31357
31954
|
init_types2();
|
|
31358
31955
|
init_ajv_provider();
|
|
31359
31956
|
init_zod_compat();
|
|
31957
|
+
init_server();
|
|
31360
31958
|
Server = class Server extends Protocol {
|
|
31361
31959
|
constructor(_serverInfo, options) {
|
|
31362
31960
|
super(options);
|
|
@@ -32498,7 +33096,7 @@ function createCompletionResult(suggestions) {
|
|
|
32498
33096
|
}
|
|
32499
33097
|
var EMPTY_OBJECT_JSON_SCHEMA, EMPTY_COMPLETION_RESULT;
|
|
32500
33098
|
var init_mcp = __esm(() => {
|
|
32501
|
-
|
|
33099
|
+
init_server2();
|
|
32502
33100
|
init_zod_compat();
|
|
32503
33101
|
init_zod_json_schema_compat();
|
|
32504
33102
|
init_types2();
|
|
@@ -32726,6 +33324,10 @@ var init_mcp2 = __esm(() => {
|
|
|
32726
33324
|
init_presence();
|
|
32727
33325
|
init_reactions();
|
|
32728
33326
|
init_locks();
|
|
33327
|
+
init_hot();
|
|
33328
|
+
init_topics();
|
|
33329
|
+
init_summary();
|
|
33330
|
+
init_graph();
|
|
32729
33331
|
import__package = __toESM(require_package(), 1);
|
|
32730
33332
|
server = new McpServer({
|
|
32731
33333
|
name: "conversations",
|
|
@@ -33366,6 +33968,106 @@ var init_mcp2 = __esm(() => {
|
|
|
33366
33968
|
content: [{ type: "text", text: JSON.stringify(messages) }]
|
|
33367
33969
|
};
|
|
33368
33970
|
});
|
|
33971
|
+
server.registerTool("build_graph", {
|
|
33972
|
+
description: "Build/rebuild the knowledge graph from messages, spaces, and projects. Creates relationship edges between agents, spaces, and projects.",
|
|
33973
|
+
inputSchema: {}
|
|
33974
|
+
}, async () => {
|
|
33975
|
+
const result = buildGraph();
|
|
33976
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
33977
|
+
});
|
|
33978
|
+
server.registerTool("get_related", {
|
|
33979
|
+
description: "Find all entities related to a given entity in the knowledge graph.",
|
|
33980
|
+
inputSchema: {
|
|
33981
|
+
entity_type: exports_external.string(),
|
|
33982
|
+
entity_id: exports_external.string()
|
|
33983
|
+
}
|
|
33984
|
+
}, async (args) => {
|
|
33985
|
+
const related = getRelated(args.entity_type, args.entity_id);
|
|
33986
|
+
return { content: [{ type: "text", text: JSON.stringify(related) }] };
|
|
33987
|
+
});
|
|
33988
|
+
server.registerTool("get_agent_network", {
|
|
33989
|
+
description: "Get an agent's communication network: who they talk to, spaces, projects.",
|
|
33990
|
+
inputSchema: {
|
|
33991
|
+
agent: exports_external.string()
|
|
33992
|
+
}
|
|
33993
|
+
}, async (args) => {
|
|
33994
|
+
const network = getAgentNetwork(args.agent);
|
|
33995
|
+
return { content: [{ type: "text", text: JSON.stringify(network) }] };
|
|
33996
|
+
});
|
|
33997
|
+
server.registerTool("graph_stats", {
|
|
33998
|
+
description: "Get knowledge graph statistics: total edges and counts by relation type.",
|
|
33999
|
+
inputSchema: {}
|
|
34000
|
+
}, async () => {
|
|
34001
|
+
const stats = getGraphStats();
|
|
34002
|
+
return { content: [{ type: "text", text: JSON.stringify(stats) }] };
|
|
34003
|
+
});
|
|
34004
|
+
server.registerTool("get_summary", {
|
|
34005
|
+
description: "Get a structured summary of a conversation (session or space): participants, topics, key messages, blockers, activity.",
|
|
34006
|
+
inputSchema: {
|
|
34007
|
+
session_id: exports_external.string().optional(),
|
|
34008
|
+
space: exports_external.string().optional(),
|
|
34009
|
+
limit: exports_external.coerce.number().optional()
|
|
34010
|
+
}
|
|
34011
|
+
}, async (args) => {
|
|
34012
|
+
const target = args.space || args.session_id;
|
|
34013
|
+
if (!target)
|
|
34014
|
+
return { content: [{ type: "text", text: "session_id or space required" }], isError: true };
|
|
34015
|
+
const summary = getConversationSummary(target, { limit: args.limit });
|
|
34016
|
+
if (!summary)
|
|
34017
|
+
return { content: [{ type: "text", text: `No messages found for "${target}"` }], isError: true };
|
|
34018
|
+
return { content: [{ type: "text", text: JSON.stringify(summary) }] };
|
|
34019
|
+
});
|
|
34020
|
+
server.registerTool("get_topics", {
|
|
34021
|
+
description: "Extract topics from a space or session. Returns weighted keyword list.",
|
|
34022
|
+
inputSchema: {
|
|
34023
|
+
space: exports_external.string().optional(),
|
|
34024
|
+
session_id: exports_external.string().optional(),
|
|
34025
|
+
limit: exports_external.coerce.number().optional()
|
|
34026
|
+
}
|
|
34027
|
+
}, async (args) => {
|
|
34028
|
+
const topics = args.space ? getSpaceTopics(args.space, { limit: args.limit }) : args.session_id ? getSessionTopics(args.session_id, { limit: args.limit }) : getTrendingTopics({ top_n: args.limit });
|
|
34029
|
+
return { content: [{ type: "text", text: JSON.stringify(topics) }] };
|
|
34030
|
+
});
|
|
34031
|
+
server.registerTool("trending_topics", {
|
|
34032
|
+
description: "Get trending topics across all messages in the last N hours.",
|
|
34033
|
+
inputSchema: {
|
|
34034
|
+
hours: exports_external.coerce.number().optional(),
|
|
34035
|
+
project_id: exports_external.string().optional(),
|
|
34036
|
+
top_n: exports_external.coerce.number().optional()
|
|
34037
|
+
}
|
|
34038
|
+
}, async (args) => {
|
|
34039
|
+
const topics = getTrendingTopics({ hours: args.hours, project_id: args.project_id, top_n: args.top_n });
|
|
34040
|
+
return { content: [{ type: "text", text: JSON.stringify(topics) }] };
|
|
34041
|
+
});
|
|
34042
|
+
server.registerTool("get_session_activity", {
|
|
34043
|
+
description: "Get activity metrics for a session: message velocity, unique agents, reply ratio, reaction count, trending status.",
|
|
34044
|
+
inputSchema: {
|
|
34045
|
+
session_id: exports_external.string()
|
|
34046
|
+
}
|
|
34047
|
+
}, async (args) => {
|
|
34048
|
+
const activity = getSessionActivity(args.session_id);
|
|
34049
|
+
if (!activity) {
|
|
34050
|
+
return { content: [{ type: "text", text: `session "${args.session_id}" not found` }], isError: true };
|
|
34051
|
+
}
|
|
34052
|
+
return { content: [{ type: "text", text: JSON.stringify(activity) }] };
|
|
34053
|
+
});
|
|
34054
|
+
server.registerTool("hot_sessions", {
|
|
34055
|
+
description: "List conversations ranked by activity hotness (message velocity, reactions, replies, priority, blockers).",
|
|
34056
|
+
inputSchema: {
|
|
34057
|
+
limit: exports_external.coerce.number().optional(),
|
|
34058
|
+
min_score: exports_external.coerce.number().optional(),
|
|
34059
|
+
space: exports_external.string().optional(),
|
|
34060
|
+
project_id: exports_external.string().optional()
|
|
34061
|
+
}
|
|
34062
|
+
}, async (args) => {
|
|
34063
|
+
const sessions = listHotSessions({
|
|
34064
|
+
limit: args.limit,
|
|
34065
|
+
min_score: args.min_score,
|
|
34066
|
+
space: args.space,
|
|
34067
|
+
project_id: args.project_id
|
|
34068
|
+
});
|
|
34069
|
+
return { content: [{ type: "text", text: JSON.stringify(sessions) }] };
|
|
34070
|
+
});
|
|
33369
34071
|
server.registerTool("add_reaction", {
|
|
33370
34072
|
description: "Add an emoji reaction to a message.",
|
|
33371
34073
|
inputSchema: {
|
|
@@ -33665,6 +34367,15 @@ var init_mcp2 = __esm(() => {
|
|
|
33665
34367
|
"pin_message",
|
|
33666
34368
|
"unpin_message",
|
|
33667
34369
|
"get_pinned_messages",
|
|
34370
|
+
"build_graph",
|
|
34371
|
+
"get_related",
|
|
34372
|
+
"get_agent_network",
|
|
34373
|
+
"graph_stats",
|
|
34374
|
+
"get_summary",
|
|
34375
|
+
"get_topics",
|
|
34376
|
+
"trending_topics",
|
|
34377
|
+
"get_session_activity",
|
|
34378
|
+
"hot_sessions",
|
|
33668
34379
|
"add_reaction",
|
|
33669
34380
|
"remove_reaction",
|
|
33670
34381
|
"get_reactions",
|
|
@@ -33723,6 +34434,15 @@ var init_mcp2 = __esm(() => {
|
|
|
33723
34434
|
pin_message: "Pin a message. Required: id",
|
|
33724
34435
|
unpin_message: "Unpin a message. Required: id",
|
|
33725
34436
|
get_pinned_messages: "Get pinned messages. Optional: space?, session_id?, limit?",
|
|
34437
|
+
build_graph: "Build/rebuild knowledge graph from messages, spaces, projects. Returns edge counts.",
|
|
34438
|
+
get_related: "Find entities related to a given entity. Required: entity_type, entity_id",
|
|
34439
|
+
get_agent_network: "Agent's communication network: contacts, spaces, projects. Required: agent",
|
|
34440
|
+
graph_stats: "Knowledge graph stats: total edges, by relation type",
|
|
34441
|
+
get_summary: "Structured conversation summary: participants, topics, key messages, blockers. Required: session_id? or space?. Optional: limit?",
|
|
34442
|
+
get_topics: "Extract topics from space or session. Optional: space?, session_id?, limit?",
|
|
34443
|
+
trending_topics: "Trending topics across all messages. Optional: hours?, project_id?, top_n?",
|
|
34444
|
+
get_session_activity: "Get activity metrics for a session: velocity, agents, reply ratio, reactions, trending. Required: session_id",
|
|
34445
|
+
hot_sessions: "List conversations by hotness score (velocity, reactions, replies, priority, blockers). Optional: limit?, min_score?, space?, project_id?",
|
|
33726
34446
|
add_reaction: "Add emoji reaction to a message. Required: message_id, emoji. Optional: from?",
|
|
33727
34447
|
remove_reaction: "Remove emoji reaction from a message. Required: message_id, emoji. Optional: from?",
|
|
33728
34448
|
get_reactions: "Get all reactions for a message. Required: message_id",
|
|
@@ -34190,6 +34910,26 @@ function startDashboardServer(port = 0, host) {
|
|
|
34190
34910
|
const agents = listAgents({ online_only: onlineOnly });
|
|
34191
34911
|
return jsonResponse(applyFields(agents, url2.searchParams.get("fields")));
|
|
34192
34912
|
}
|
|
34913
|
+
if (path === "/api/sessions/hot" && req.method === "GET") {
|
|
34914
|
+
const limit = url2.searchParams.get("limit") ? parseInt(url2.searchParams.get("limit")) : undefined;
|
|
34915
|
+
const min_score = url2.searchParams.get("min_score") ? parseInt(url2.searchParams.get("min_score")) : undefined;
|
|
34916
|
+
const space = url2.searchParams.get("space") ?? undefined;
|
|
34917
|
+
const project_id = url2.searchParams.get("project_id") ?? undefined;
|
|
34918
|
+
const sessions = listHotSessions({ limit, min_score, space, project_id });
|
|
34919
|
+
return jsonResponse(sessions);
|
|
34920
|
+
}
|
|
34921
|
+
if (path === "/api/graph" && req.method === "GET") {
|
|
34922
|
+
const entityType = url2.searchParams.get("entity_type");
|
|
34923
|
+
const entityId = url2.searchParams.get("entity_id");
|
|
34924
|
+
if (entityType && entityId) {
|
|
34925
|
+
return jsonResponse(getRelated(entityType, entityId));
|
|
34926
|
+
}
|
|
34927
|
+
return jsonResponse(getGraphStats());
|
|
34928
|
+
}
|
|
34929
|
+
const agentNetMatch = path.match(/^\/api\/graph\/agent\/(.+)$/);
|
|
34930
|
+
if (agentNetMatch && req.method === "GET") {
|
|
34931
|
+
return jsonResponse(getAgentNetwork(decodeURIComponent(agentNetMatch[1])));
|
|
34932
|
+
}
|
|
34193
34933
|
if (path === "/api/reactions" && req.method === "GET") {
|
|
34194
34934
|
const messageIdStr = url2.searchParams.get("message_id");
|
|
34195
34935
|
if (!messageIdStr)
|
|
@@ -34288,6 +35028,8 @@ var init_serve = __esm(() => {
|
|
|
34288
35028
|
init_db();
|
|
34289
35029
|
init_presence();
|
|
34290
35030
|
init_reactions();
|
|
35031
|
+
init_hot();
|
|
35032
|
+
init_graph();
|
|
34291
35033
|
init_locks();
|
|
34292
35034
|
isDirectRun2 = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("serve.ts") || process.argv[1]?.endsWith("serve.js");
|
|
34293
35035
|
if (isDirectRun2) {
|
|
@@ -34321,6 +35063,10 @@ init_db();
|
|
|
34321
35063
|
init_identity();
|
|
34322
35064
|
init_presence();
|
|
34323
35065
|
init_reactions();
|
|
35066
|
+
init_hot();
|
|
35067
|
+
init_topics();
|
|
35068
|
+
init_summary();
|
|
35069
|
+
init_graph();
|
|
34324
35070
|
init_terminal_markdown();
|
|
34325
35071
|
import chalk3 from "chalk";
|
|
34326
35072
|
import { render } from "ink";
|
|
@@ -35420,6 +36166,142 @@ program2.command("since").description("Show all activity (DMs + spaces) since a
|
|
|
35420
36166
|
}
|
|
35421
36167
|
closeDb();
|
|
35422
36168
|
});
|
|
36169
|
+
var graph = program2.command("graph").description("Knowledge graph operations");
|
|
36170
|
+
graph.command("build").description("Build/rebuild knowledge graph from messages, spaces, projects").option("--json", "Output as JSON").action((opts) => {
|
|
36171
|
+
const result = buildGraph();
|
|
36172
|
+
if (opts.json) {
|
|
36173
|
+
console.log(JSON.stringify(result, null, 2));
|
|
36174
|
+
} else {
|
|
36175
|
+
console.log(chalk3.green(`Graph built: ${result.edges_created} created, ${result.edges_updated} updated`));
|
|
36176
|
+
}
|
|
36177
|
+
closeDb();
|
|
36178
|
+
});
|
|
36179
|
+
graph.command("stats").description("Show knowledge graph statistics").option("--json", "Output as JSON").action((opts) => {
|
|
36180
|
+
const stats = getGraphStats();
|
|
36181
|
+
if (opts.json) {
|
|
36182
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
36183
|
+
} else {
|
|
36184
|
+
console.log(chalk3.bold(`Knowledge Graph: ${stats.total_edges} edges
|
|
36185
|
+
`));
|
|
36186
|
+
for (const [relation, count] of Object.entries(stats.by_relation)) {
|
|
36187
|
+
console.log(` ${chalk3.cyan(relation.padEnd(20))} ${count}`);
|
|
36188
|
+
}
|
|
36189
|
+
}
|
|
36190
|
+
closeDb();
|
|
36191
|
+
});
|
|
36192
|
+
graph.command("agent").description("Show an agent's communication network").argument("<name>", "Agent name").option("--json", "Output as JSON").action((name, opts) => {
|
|
36193
|
+
const network = getAgentNetwork(name);
|
|
36194
|
+
if (opts.json) {
|
|
36195
|
+
console.log(JSON.stringify(network, null, 2));
|
|
36196
|
+
} else {
|
|
36197
|
+
console.log(chalk3.bold(`Network for ${chalk3.cyan(name)}
|
|
36198
|
+
`));
|
|
36199
|
+
if (network.communicates_with.length > 0) {
|
|
36200
|
+
console.log(chalk3.bold(" Communicates with:"));
|
|
36201
|
+
for (const c of network.communicates_with) {
|
|
36202
|
+
console.log(` ${chalk3.cyan(c.agent.padEnd(20))} ${chalk3.dim(`${c.message_count} msgs`)}`);
|
|
36203
|
+
}
|
|
36204
|
+
}
|
|
36205
|
+
if (network.spaces.length > 0) {
|
|
36206
|
+
console.log(chalk3.bold(" Active spaces:"));
|
|
36207
|
+
for (const s of network.spaces) {
|
|
36208
|
+
console.log(` ${chalk3.magenta("#" + s.space.padEnd(19))} ${chalk3.dim(`${s.message_count} msgs`)}`);
|
|
36209
|
+
}
|
|
36210
|
+
}
|
|
36211
|
+
if (network.projects.length > 0) {
|
|
36212
|
+
console.log(chalk3.bold(" Projects:") + " " + network.projects.join(", "));
|
|
36213
|
+
}
|
|
36214
|
+
}
|
|
36215
|
+
closeDb();
|
|
36216
|
+
});
|
|
36217
|
+
program2.command("summary").description("Get a structured summary of a conversation").argument("<target>", "Session ID or space name").option("--json", "Output as JSON").action((target, opts) => {
|
|
36218
|
+
const summary = getConversationSummary(target);
|
|
36219
|
+
if (!summary) {
|
|
36220
|
+
console.error(chalk3.red(`No messages found for "${target}"`));
|
|
36221
|
+
process.exit(1);
|
|
36222
|
+
}
|
|
36223
|
+
if (opts.json) {
|
|
36224
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
36225
|
+
} else {
|
|
36226
|
+
console.log(chalk3.bold(`Summary: ${target}
|
|
36227
|
+
`));
|
|
36228
|
+
console.log(` ${chalk3.bold("Participants:")} ${summary.participants.join(", ")}`);
|
|
36229
|
+
console.log(` ${chalk3.bold("Messages:")} ${summary.message_count}`);
|
|
36230
|
+
console.log(` ${chalk3.bold("Date range:")} ${summary.date_range.first.slice(0, 16)} \u2192 ${summary.date_range.last.slice(0, 16)}`);
|
|
36231
|
+
console.log(` ${chalk3.bold("Replies:")} ${summary.activity.reply_count} ${chalk3.bold("Reactions:")} ${summary.activity.reaction_count}`);
|
|
36232
|
+
if (summary.topics.length > 0) {
|
|
36233
|
+
console.log(`
|
|
36234
|
+
${chalk3.bold("Topics:")} ${summary.topics.slice(0, 5).map((t) => t.topic).join(", ")}`);
|
|
36235
|
+
}
|
|
36236
|
+
if (summary.key_messages.length > 0) {
|
|
36237
|
+
console.log(`
|
|
36238
|
+
${chalk3.bold("Key messages:")}`);
|
|
36239
|
+
for (const k of summary.key_messages.slice(0, 5)) {
|
|
36240
|
+
console.log(` [#${k.id}] ${chalk3.cyan(k.from)} (${chalk3.yellow(k.reason)}): ${k.content.slice(0, 80)}`);
|
|
36241
|
+
}
|
|
36242
|
+
}
|
|
36243
|
+
if (summary.unresolved_blockers.length > 0) {
|
|
36244
|
+
console.log(`
|
|
36245
|
+
${chalk3.red.bold("Unresolved blockers:")}`);
|
|
36246
|
+
for (const b of summary.unresolved_blockers) {
|
|
36247
|
+
console.log(` ${chalk3.red("[BLOCKER]")} [#${b.id}] ${chalk3.cyan(b.from)}: ${b.content.slice(0, 80)}`);
|
|
36248
|
+
}
|
|
36249
|
+
}
|
|
36250
|
+
}
|
|
36251
|
+
closeDb();
|
|
36252
|
+
});
|
|
36253
|
+
program2.command("topics").description("Extract topics from a space, session, or trending globally").option("--space <name>", "Topics for a specific space").option("--session <id>", "Topics for a specific session").option("--hours <n>", "Trending topics in last N hours", parseInt).option("--json", "Output as JSON").action((opts) => {
|
|
36254
|
+
let topics;
|
|
36255
|
+
if (opts.space) {
|
|
36256
|
+
topics = getSpaceTopics(opts.space);
|
|
36257
|
+
} else if (opts.session) {
|
|
36258
|
+
topics = getSessionTopics(opts.session);
|
|
36259
|
+
} else {
|
|
36260
|
+
topics = getTrendingTopics({ hours: opts.hours ?? 24 });
|
|
36261
|
+
}
|
|
36262
|
+
if (opts.json) {
|
|
36263
|
+
console.log(JSON.stringify(topics, null, 2));
|
|
36264
|
+
} else {
|
|
36265
|
+
if (topics.length === 0) {
|
|
36266
|
+
console.log(chalk3.dim("No topics found."));
|
|
36267
|
+
} else {
|
|
36268
|
+
const label = opts.space ? `#${opts.space}` : opts.session ? opts.session : `last ${opts.hours ?? 24}h`;
|
|
36269
|
+
console.log(chalk3.bold(`Topics for ${label}
|
|
36270
|
+
`));
|
|
36271
|
+
for (const t of topics) {
|
|
36272
|
+
const bar = "\u2588".repeat(Math.min(Math.round(t.weight * 50), 30));
|
|
36273
|
+
console.log(` ${chalk3.cyan(t.topic.padEnd(20))} ${chalk3.dim(`\xD7${t.count}`)} ${chalk3.green(bar)}`);
|
|
36274
|
+
}
|
|
36275
|
+
}
|
|
36276
|
+
}
|
|
36277
|
+
closeDb();
|
|
36278
|
+
});
|
|
36279
|
+
program2.command("hot").description("Show hot conversations ranked by activity").option("--limit <n>", "Max results", parseInt).option("--min-score <n>", "Minimum hotness score", parseInt).option("--space <name>", "Filter by space").option("--json", "Output as JSON").action((opts) => {
|
|
36280
|
+
const sessions = listHotSessions({
|
|
36281
|
+
limit: opts.limit ?? 10,
|
|
36282
|
+
min_score: opts.minScore,
|
|
36283
|
+
space: opts.space
|
|
36284
|
+
});
|
|
36285
|
+
if (opts.json) {
|
|
36286
|
+
console.log(JSON.stringify(sessions, null, 2));
|
|
36287
|
+
} else {
|
|
36288
|
+
if (sessions.length === 0) {
|
|
36289
|
+
console.log(chalk3.dim("No hot conversations."));
|
|
36290
|
+
} else {
|
|
36291
|
+
console.log(chalk3.bold(`Hot Conversations
|
|
36292
|
+
`));
|
|
36293
|
+
for (const s of sessions) {
|
|
36294
|
+
const score = s.hotness_score > 20 ? chalk3.red(`\uD83D\uDD25 ${s.hotness_score}`) : chalk3.yellow(` ${s.hotness_score}`);
|
|
36295
|
+
const where = s.space ? chalk3.magenta(`#${s.space}`) : chalk3.cyan(s.participants.join(", "));
|
|
36296
|
+
const time3 = chalk3.dim(s.last_message_at.slice(11, 16));
|
|
36297
|
+
const msgs = chalk3.dim(`${s.message_count} msgs`);
|
|
36298
|
+
const agents = chalk3.dim(`${s.metrics.unique_agents} agents`);
|
|
36299
|
+
console.log(`${score} ${where} ${time3} ${msgs} ${agents}`);
|
|
36300
|
+
}
|
|
36301
|
+
}
|
|
36302
|
+
}
|
|
36303
|
+
closeDb();
|
|
36304
|
+
});
|
|
35423
36305
|
program2.command("context").description("One-shot session boot context for agents: online agents, unread DMs, spaces, recent activity").option("--json", "Output as JSON").action((opts) => {
|
|
35424
36306
|
const agent = resolveIdentity();
|
|
35425
36307
|
heartbeat(agent);
|