@hasna/conversations 0.2.45 → 0.2.46

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.js CHANGED
@@ -9680,6 +9680,18 @@ function getDb() {
9680
9680
  PRIMARY KEY (space, agent)
9681
9681
  )
9682
9682
  `);
9683
+ db.exec(`
9684
+ CREATE TABLE IF NOT EXISTS space_subscriptions (
9685
+ space TEXT NOT NULL REFERENCES spaces(name),
9686
+ agent TEXT NOT NULL,
9687
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
9688
+ preview_chars INTEGER NOT NULL DEFAULT 140,
9689
+ since_message_id INTEGER NOT NULL DEFAULT 0,
9690
+ PRIMARY KEY (space, agent)
9691
+ )
9692
+ `);
9693
+ db.exec("CREATE INDEX IF NOT EXISTS idx_space_subscriptions_agent ON space_subscriptions(agent)");
9694
+ db.exec("CREATE INDEX IF NOT EXISTS idx_space_subscriptions_space ON space_subscriptions(space)");
9683
9695
  db.exec(`
9684
9696
  CREATE TABLE IF NOT EXISTS agent_presence (
9685
9697
  id TEXT NOT NULL,
@@ -9720,6 +9732,16 @@ function getDb() {
9720
9732
  )
9721
9733
  `);
9722
9734
  db.exec("CREATE INDEX IF NOT EXISTS idx_reactions_message ON reactions(message_id)");
9735
+ db.exec(`
9736
+ CREATE TABLE IF NOT EXISTS space_notification_reads (
9737
+ agent TEXT NOT NULL,
9738
+ message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
9739
+ read_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
9740
+ PRIMARY KEY (agent, message_id)
9741
+ )
9742
+ `);
9743
+ db.exec("CREATE INDEX IF NOT EXISTS idx_space_notification_reads_agent ON space_notification_reads(agent)");
9744
+ db.exec("CREATE INDEX IF NOT EXISTS idx_space_notification_reads_message ON space_notification_reads(message_id)");
9723
9745
  const existingTables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();
9724
9746
  const tableNames = existingTables.map((t) => t.name);
9725
9747
  if (tableNames.includes("channels") && tableNames.includes("spaces")) {
@@ -9767,6 +9789,19 @@ function getDb() {
9767
9789
  if (!spaceColNames.includes("topic")) {
9768
9790
  db.exec("ALTER TABLE spaces ADD COLUMN topic TEXT");
9769
9791
  }
9792
+ const spaceSubscriptionCols = db.prepare("PRAGMA table_info(space_subscriptions)").all();
9793
+ const spaceSubscriptionColNames = spaceSubscriptionCols.map((c) => c.name);
9794
+ if (!spaceSubscriptionColNames.includes("since_message_id")) {
9795
+ db.exec("ALTER TABLE space_subscriptions ADD COLUMN since_message_id INTEGER NOT NULL DEFAULT 0");
9796
+ db.exec(`
9797
+ UPDATE space_subscriptions
9798
+ SET since_message_id = COALESCE(
9799
+ (SELECT MAX(m.id) FROM messages m WHERE m.space = space_subscriptions.space),
9800
+ 0
9801
+ )
9802
+ WHERE since_message_id = 0
9803
+ `);
9804
+ }
9770
9805
  const msgCols2 = db.prepare("PRAGMA table_info(messages)").all();
9771
9806
  const colNames2 = msgCols2.map((c) => c.name);
9772
9807
  if (!colNames2.includes("edited_at")) {
@@ -12568,6 +12603,122 @@ function isSpaceMember(spaceName, agent) {
12568
12603
  const row = db2.prepare("SELECT 1 FROM space_members WHERE space = ? AND agent = ?").get(spaceName, agent);
12569
12604
  return !!row;
12570
12605
  }
12606
+ // src/lib/space-notifications.ts
12607
+ init_db();
12608
+ var DEFAULT_PREVIEW_CHARS = 140;
12609
+ function buildMessagePreview(content, maxChars = DEFAULT_PREVIEW_CHARS) {
12610
+ const normalized = content.replace(/[*#`~_>\-]/g, " ").replace(/\s+/g, " ").trim();
12611
+ if (normalized.length <= maxChars)
12612
+ return normalized;
12613
+ return normalized.slice(0, Math.max(1, maxChars)).trimEnd() + "\u2026";
12614
+ }
12615
+ function subscribeToSpaceNotifications(space, agent, opts) {
12616
+ const db2 = getDb();
12617
+ const existingSpace = db2.prepare("SELECT name FROM spaces WHERE name = ?").get(space);
12618
+ if (!existingSpace) {
12619
+ throw new Error(`Space not found: ${space}`);
12620
+ }
12621
+ const previewChars = Number.isFinite(opts?.preview_chars) && opts?.preview_chars > 0 ? Math.floor(opts.preview_chars) : DEFAULT_PREVIEW_CHARS;
12622
+ const currentMaxMessageId = db2.prepare("SELECT COALESCE(MAX(id), 0) AS max_id FROM messages WHERE space = ?").get(space).max_id;
12623
+ db2.prepare(`
12624
+ INSERT INTO space_subscriptions (space, agent, preview_chars, since_message_id)
12625
+ VALUES (?, ?, ?, ?)
12626
+ ON CONFLICT(space, agent) DO UPDATE SET preview_chars = excluded.preview_chars
12627
+ `).run(space, agent, previewChars, currentMaxMessageId);
12628
+ return db2.prepare("SELECT space, agent, created_at, preview_chars, since_message_id FROM space_subscriptions WHERE space = ? AND agent = ?").get(space, agent);
12629
+ }
12630
+ function unsubscribeFromSpaceNotifications(space, agent) {
12631
+ const db2 = getDb();
12632
+ const result = db2.prepare("DELETE FROM space_subscriptions WHERE space = ? AND agent = ?").run(space, agent);
12633
+ return result.changes > 0;
12634
+ }
12635
+ function listSpaceNotificationSubscriptions(agent) {
12636
+ const db2 = getDb();
12637
+ if (agent) {
12638
+ return db2.prepare("SELECT space, agent, created_at, preview_chars, since_message_id FROM space_subscriptions WHERE agent = ? ORDER BY created_at ASC, space ASC").all(agent);
12639
+ }
12640
+ return db2.prepare("SELECT space, agent, created_at, preview_chars, since_message_id FROM space_subscriptions ORDER BY agent ASC, space ASC").all();
12641
+ }
12642
+ function getSubscribedSpaces(agent) {
12643
+ return listSpaceNotificationSubscriptions(agent).map((row) => row.space);
12644
+ }
12645
+ function readSpaceNotifications(opts) {
12646
+ const db2 = getDb();
12647
+ const conditions = [
12648
+ "s.agent = ?",
12649
+ "m.space IS NOT NULL",
12650
+ "m.from_agent != ?",
12651
+ "m.id > s.since_message_id"
12652
+ ];
12653
+ const params = [opts.agent, opts.agent];
12654
+ if (opts.space) {
12655
+ conditions.push("m.space = ?");
12656
+ params.push(opts.space);
12657
+ }
12658
+ if (opts.since) {
12659
+ conditions.push("m.created_at > ?");
12660
+ params.push(opts.since);
12661
+ }
12662
+ if (opts.unread_only !== false) {
12663
+ conditions.push("snr.message_id IS NULL");
12664
+ }
12665
+ const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
12666
+ const rows = db2.prepare(`
12667
+ SELECT
12668
+ m.id AS message_id,
12669
+ m.space,
12670
+ m.from_agent,
12671
+ m.created_at,
12672
+ m.priority,
12673
+ m.content,
12674
+ m.attachments,
12675
+ s.preview_chars,
12676
+ snr.message_id AS read_message_id
12677
+ FROM messages m
12678
+ INNER JOIN space_subscriptions s
12679
+ ON s.space = m.space
12680
+ LEFT JOIN space_notification_reads snr
12681
+ ON snr.message_id = m.id AND snr.agent = s.agent
12682
+ WHERE ${conditions.join(" AND ")}
12683
+ ORDER BY m.created_at DESC, m.id DESC
12684
+ LIMIT ${Math.max(1, Math.min(limit, 500))}
12685
+ `).all(...params);
12686
+ const notifications = rows.map((row) => ({
12687
+ message_id: row.message_id,
12688
+ space: row.space,
12689
+ from_agent: row.from_agent,
12690
+ created_at: row.created_at,
12691
+ priority: row.priority,
12692
+ preview: buildMessagePreview(row.content, row.preview_chars),
12693
+ unread: row.read_message_id == null,
12694
+ has_attachments: !!row.attachments && row.attachments !== "[]"
12695
+ }));
12696
+ if (opts.mark_read && notifications.length > 0) {
12697
+ markSpaceNotificationsRead(opts.agent, notifications.map((row) => row.message_id));
12698
+ for (const row of notifications)
12699
+ row.unread = false;
12700
+ }
12701
+ return notifications;
12702
+ }
12703
+ function markSpaceNotificationsRead(agent, messageIds) {
12704
+ if (messageIds.length === 0)
12705
+ return 0;
12706
+ const db2 = getDb();
12707
+ const insert = db2.prepare(`
12708
+ INSERT OR IGNORE INTO space_notification_reads (agent, message_id)
12709
+ VALUES (?, ?)
12710
+ `);
12711
+ let count = 0;
12712
+ for (const id of messageIds) {
12713
+ const result = insert.run(agent, id);
12714
+ count += result.changes;
12715
+ }
12716
+ return count;
12717
+ }
12718
+ function markAllSpaceNotificationsRead(agent, space) {
12719
+ const unread = readSpaceNotifications({ agent, space, unread_only: true, limit: 1e4 });
12720
+ return markSpaceNotificationsRead(agent, unread.map((row) => row.message_id));
12721
+ }
12571
12722
  // src/lib/projects.ts
12572
12723
  init_db();
12573
12724
  import { randomUUID as randomUUID2 } from "crypto";
@@ -14221,9 +14372,11 @@ export {
14221
14372
  useMessages,
14222
14373
  updateSpace,
14223
14374
  updateProject,
14375
+ unsubscribeFromSpaceNotifications,
14224
14376
  unpinMessage,
14225
14377
  unarchiveSpace,
14226
14378
  tryBulkAcquireLock,
14379
+ subscribeToSpaceNotifications,
14227
14380
  startPolling,
14228
14381
  setActiveModel,
14229
14382
  sendMessage,
@@ -14236,13 +14389,17 @@ export {
14236
14389
  releaseStaleAgentLocks,
14237
14390
  releaseLock,
14238
14391
  registerAgent,
14392
+ readSpaceNotifications,
14239
14393
  readMessages,
14240
14394
  pinMessage,
14241
14395
  markSpaceRead,
14396
+ markSpaceNotificationsRead,
14242
14397
  markSessionRead,
14243
14398
  markRead,
14399
+ markAllSpaceNotificationsRead,
14244
14400
  markAllRead,
14245
14401
  listSpaces,
14402
+ listSpaceNotificationSubscriptions,
14246
14403
  listSessions,
14247
14404
  listProjects,
14248
14405
  listLocksEnriched,
@@ -14257,6 +14414,7 @@ export {
14257
14414
  getUnreadBlockers,
14258
14415
  getTrendingTopics,
14259
14416
  getThreadReplies,
14417
+ getSubscribedSpaces,
14260
14418
  getSpaceTopics,
14261
14419
  getSpaceMembers,
14262
14420
  getSpaceDepth,
@@ -14292,6 +14450,7 @@ export {
14292
14450
  clearActiveModel,
14293
14451
  cleanExpiredLocks,
14294
14452
  checkLock,
14453
+ buildMessagePreview,
14295
14454
  buildGraph,
14296
14455
  archiveSpace,
14297
14456
  addReaction,
@@ -0,0 +1,19 @@
1
+ import type { SpaceNotification, SpaceNotificationSubscription } from "../types.js";
2
+ export declare function buildMessagePreview(content: string, maxChars?: number): string;
3
+ export declare function subscribeToSpaceNotifications(space: string, agent: string, opts?: {
4
+ preview_chars?: number;
5
+ }): SpaceNotificationSubscription;
6
+ export declare function unsubscribeFromSpaceNotifications(space: string, agent: string): boolean;
7
+ export declare function listSpaceNotificationSubscriptions(agent?: string): SpaceNotificationSubscription[];
8
+ export declare function getSubscribedSpaces(agent: string): string[];
9
+ export interface ReadSpaceNotificationsOptions {
10
+ agent: string;
11
+ space?: string;
12
+ unread_only?: boolean;
13
+ limit?: number;
14
+ since?: string;
15
+ mark_read?: boolean;
16
+ }
17
+ export declare function readSpaceNotifications(opts: ReadSpaceNotificationsOptions): SpaceNotification[];
18
+ export declare function markSpaceNotificationsRead(agent: string, messageIds: number[]): number;
19
+ export declare function markAllSpaceNotificationsRead(agent: string, space?: string): number;
@@ -0,0 +1 @@
1
+ export {};
@@ -21,4 +21,7 @@ export declare function setSessionAgent(agentId: string, claudeSessionId?: strin
21
21
  export declare function setClaudeSessionId(id: string): void;
22
22
  export declare function getSessionAgent(): string | null;
23
23
  export declare function getClaudeSessionId(): string | null;
24
- export declare function registerChannelBridge(server: McpServer): void;
24
+ export declare function registerChannelBridge(server: McpServer, opts?: {
25
+ pollIntervalMs?: number;
26
+ startDelayMs?: number;
27
+ }): () => void;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Messaging tools: send_message, read_messages, read_digest, list_sessions, reply,
2
+ * Messaging tools: send_message, read_messages, get_message, read_digest, list_sessions, reply,
3
3
  * mark_read, mark_unread, mark_space_read, search_messages, export_messages,
4
4
  * delete_message, edit_message, pin_message, unpin_message, get_pinned_messages,
5
5
  * mark_all_read, broadcast
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Space tools: create_space, list_spaces, send_to_space, read_space,
3
3
  * join_space, leave_space, update_space, archive_space, unarchive_space,
4
+ * subscribe_space_notifications, unsubscribe_space_notifications,
5
+ * list_space_subscriptions, read_space_notifications, mark_space_notifications_read,
4
6
  * set_space_topic, get_space_topic, summarize_space
5
7
  */
6
8
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
package/dist/types.d.ts CHANGED
@@ -56,6 +56,23 @@ export interface SpaceMember {
56
56
  agent: string;
57
57
  joined_at: string;
58
58
  }
59
+ export interface SpaceNotificationSubscription {
60
+ space: string;
61
+ agent: string;
62
+ created_at: string;
63
+ preview_chars: number;
64
+ since_message_id: number;
65
+ }
66
+ export interface SpaceNotification {
67
+ message_id: number;
68
+ space: string;
69
+ from_agent: string;
70
+ created_at: string;
71
+ priority: Priority;
72
+ preview: string;
73
+ unread: boolean;
74
+ has_attachments: boolean;
75
+ }
59
76
  export interface SpaceInfo extends Space {
60
77
  member_count: number;
61
78
  message_count: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/conversations",
3
- "version": "0.2.45",
3
+ "version": "0.2.46",
4
4
  "description": "Real-time CLI messaging for AI agents",
5
5
  "type": "module",
6
6
  "bin": {