@hasna/conversations 0.2.45 → 0.2.47

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/mcp.js CHANGED
@@ -17851,6 +17851,18 @@ function getDb() {
17851
17851
  PRIMARY KEY (space, agent)
17852
17852
  )
17853
17853
  `);
17854
+ db.exec(`
17855
+ CREATE TABLE IF NOT EXISTS space_subscriptions (
17856
+ space TEXT NOT NULL REFERENCES spaces(name),
17857
+ agent TEXT NOT NULL,
17858
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
17859
+ preview_chars INTEGER NOT NULL DEFAULT 140,
17860
+ since_message_id INTEGER NOT NULL DEFAULT 0,
17861
+ PRIMARY KEY (space, agent)
17862
+ )
17863
+ `);
17864
+ db.exec("CREATE INDEX IF NOT EXISTS idx_space_subscriptions_agent ON space_subscriptions(agent)");
17865
+ db.exec("CREATE INDEX IF NOT EXISTS idx_space_subscriptions_space ON space_subscriptions(space)");
17854
17866
  db.exec(`
17855
17867
  CREATE TABLE IF NOT EXISTS agent_presence (
17856
17868
  id TEXT NOT NULL,
@@ -17891,6 +17903,16 @@ function getDb() {
17891
17903
  )
17892
17904
  `);
17893
17905
  db.exec("CREATE INDEX IF NOT EXISTS idx_reactions_message ON reactions(message_id)");
17906
+ db.exec(`
17907
+ CREATE TABLE IF NOT EXISTS space_notification_reads (
17908
+ agent TEXT NOT NULL,
17909
+ message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
17910
+ read_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
17911
+ PRIMARY KEY (agent, message_id)
17912
+ )
17913
+ `);
17914
+ db.exec("CREATE INDEX IF NOT EXISTS idx_space_notification_reads_agent ON space_notification_reads(agent)");
17915
+ db.exec("CREATE INDEX IF NOT EXISTS idx_space_notification_reads_message ON space_notification_reads(message_id)");
17894
17916
  const existingTables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();
17895
17917
  const tableNames = existingTables.map((t) => t.name);
17896
17918
  if (tableNames.includes("channels") && tableNames.includes("spaces")) {
@@ -17938,6 +17960,19 @@ function getDb() {
17938
17960
  if (!spaceColNames.includes("topic")) {
17939
17961
  db.exec("ALTER TABLE spaces ADD COLUMN topic TEXT");
17940
17962
  }
17963
+ const spaceSubscriptionCols = db.prepare("PRAGMA table_info(space_subscriptions)").all();
17964
+ const spaceSubscriptionColNames = spaceSubscriptionCols.map((c) => c.name);
17965
+ if (!spaceSubscriptionColNames.includes("since_message_id")) {
17966
+ db.exec("ALTER TABLE space_subscriptions ADD COLUMN since_message_id INTEGER NOT NULL DEFAULT 0");
17967
+ db.exec(`
17968
+ UPDATE space_subscriptions
17969
+ SET since_message_id = COALESCE(
17970
+ (SELECT MAX(m.id) FROM messages m WHERE m.space = space_subscriptions.space),
17971
+ 0
17972
+ )
17973
+ WHERE since_message_id = 0
17974
+ `);
17975
+ }
17941
17976
  const msgCols2 = db.prepare("PRAGMA table_info(messages)").all();
17942
17977
  const colNames2 = msgCols2.map((c) => c.name);
17943
17978
  if (!colNames2.includes("edited_at")) {
@@ -18558,6 +18593,24 @@ var init_pg_migrations = __esm(() => {
18558
18593
  PRIMARY KEY (space, agent)
18559
18594
  );
18560
18595
 
18596
+ CREATE TABLE IF NOT EXISTS space_subscriptions (
18597
+ space TEXT NOT NULL REFERENCES spaces(name),
18598
+ agent TEXT NOT NULL,
18599
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
18600
+ preview_chars INTEGER NOT NULL DEFAULT 140,
18601
+ since_message_id BIGINT NOT NULL DEFAULT 0,
18602
+ PRIMARY KEY (space, agent)
18603
+ );
18604
+ CREATE INDEX IF NOT EXISTS idx_space_subscriptions_agent ON space_subscriptions(agent);
18605
+ CREATE INDEX IF NOT EXISTS idx_space_subscriptions_space ON space_subscriptions(space);
18606
+ ALTER TABLE space_subscriptions ADD COLUMN IF NOT EXISTS since_message_id BIGINT NOT NULL DEFAULT 0;
18607
+ UPDATE space_subscriptions ss
18608
+ SET since_message_id = COALESCE(
18609
+ (SELECT MAX(m.id) FROM messages m WHERE m.space = ss.space),
18610
+ 0
18611
+ )
18612
+ WHERE ss.since_message_id = 0;
18613
+
18561
18614
  CREATE TABLE IF NOT EXISTS messages (
18562
18615
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
18563
18616
  uuid TEXT NOT NULL DEFAULT gen_random_uuid()::text UNIQUE,
@@ -18635,6 +18688,15 @@ var init_pg_migrations = __esm(() => {
18635
18688
  CREATE INDEX IF NOT EXISTS idx_read_receipts_message ON message_read_receipts(message_id);
18636
18689
  CREATE INDEX IF NOT EXISTS idx_read_receipts_agent ON message_read_receipts(agent);
18637
18690
 
18691
+ CREATE TABLE IF NOT EXISTS space_notification_reads (
18692
+ agent TEXT NOT NULL,
18693
+ message_id BIGINT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
18694
+ read_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
18695
+ PRIMARY KEY (agent, message_id)
18696
+ );
18697
+ CREATE INDEX IF NOT EXISTS idx_space_notification_reads_agent ON space_notification_reads(agent);
18698
+ CREATE INDEX IF NOT EXISTS idx_space_notification_reads_message ON space_notification_reads(message_id);
18699
+
18638
18700
  CREATE TABLE IF NOT EXISTS message_mentions (
18639
18701
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
18640
18702
  message_id BIGINT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
@@ -41663,6 +41725,23 @@ function registerMessagingTools(server, resolveProjectId) {
41663
41725
  content: [{ type: "text", text: JSON.stringify({ messages, count: messages.length, offset: args.offset ?? 0 }) }]
41664
41726
  };
41665
41727
  });
41728
+ server.registerTool("get_message", {
41729
+ description: "Get the full content of a message by numeric ID. Use this to inspect a full space message after receiving a preview-only notification blurb.",
41730
+ inputSchema: {
41731
+ id: exports_external.coerce.number().describe("Numeric message ID to fetch")
41732
+ }
41733
+ }, async (args) => {
41734
+ const message = getMessageById(args.id);
41735
+ if (!message) {
41736
+ return {
41737
+ content: [{ type: "text", text: `Message #${args.id} not found` }],
41738
+ isError: true
41739
+ };
41740
+ }
41741
+ return {
41742
+ content: [{ type: "text", text: JSON.stringify(message) }]
41743
+ };
41744
+ });
41666
41745
  server.registerTool("list_sessions", {
41667
41746
  description: "List all sessions by agent.",
41668
41747
  inputSchema: {
@@ -42106,6 +42185,120 @@ function unarchiveSpace(name) {
42106
42185
  return row;
42107
42186
  }
42108
42187
 
42188
+ // src/lib/space-notifications.ts
42189
+ init_db();
42190
+ var DEFAULT_PREVIEW_CHARS = 140;
42191
+ function buildMessagePreview(content, maxChars = DEFAULT_PREVIEW_CHARS) {
42192
+ const normalized = content.replace(/[*#`~_>\-]/g, " ").replace(/\s+/g, " ").trim();
42193
+ if (normalized.length <= maxChars)
42194
+ return normalized;
42195
+ return normalized.slice(0, Math.max(1, maxChars)).trimEnd() + "\u2026";
42196
+ }
42197
+ function subscribeToSpaceNotifications(space, agent, opts) {
42198
+ const db2 = getDb();
42199
+ const existingSpace = db2.prepare("SELECT name FROM spaces WHERE name = ?").get(space);
42200
+ if (!existingSpace) {
42201
+ throw new Error(`Space not found: ${space}`);
42202
+ }
42203
+ const previewChars = Number.isFinite(opts?.preview_chars) && opts?.preview_chars > 0 ? Math.floor(opts.preview_chars) : DEFAULT_PREVIEW_CHARS;
42204
+ const currentMaxMessageId = db2.prepare("SELECT COALESCE(MAX(id), 0) AS max_id FROM messages WHERE space = ?").get(space).max_id;
42205
+ db2.prepare(`
42206
+ INSERT INTO space_subscriptions (space, agent, preview_chars, since_message_id)
42207
+ VALUES (?, ?, ?, ?)
42208
+ ON CONFLICT(space, agent) DO UPDATE SET preview_chars = excluded.preview_chars
42209
+ `).run(space, agent, previewChars, currentMaxMessageId);
42210
+ return db2.prepare("SELECT space, agent, created_at, preview_chars, since_message_id FROM space_subscriptions WHERE space = ? AND agent = ?").get(space, agent);
42211
+ }
42212
+ function unsubscribeFromSpaceNotifications(space, agent) {
42213
+ const db2 = getDb();
42214
+ const result = db2.prepare("DELETE FROM space_subscriptions WHERE space = ? AND agent = ?").run(space, agent);
42215
+ return result.changes > 0;
42216
+ }
42217
+ function listSpaceNotificationSubscriptions(agent) {
42218
+ const db2 = getDb();
42219
+ if (agent) {
42220
+ 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);
42221
+ }
42222
+ return db2.prepare("SELECT space, agent, created_at, preview_chars, since_message_id FROM space_subscriptions ORDER BY agent ASC, space ASC").all();
42223
+ }
42224
+ function readSpaceNotifications(opts) {
42225
+ const db2 = getDb();
42226
+ const conditions = [
42227
+ "s.agent = ?",
42228
+ "m.space IS NOT NULL",
42229
+ "m.from_agent != ?",
42230
+ "m.id > s.since_message_id"
42231
+ ];
42232
+ const params = [opts.agent, opts.agent];
42233
+ if (opts.space) {
42234
+ conditions.push("m.space = ?");
42235
+ params.push(opts.space);
42236
+ }
42237
+ if (opts.since) {
42238
+ conditions.push("m.created_at > ?");
42239
+ params.push(opts.since);
42240
+ }
42241
+ if (opts.unread_only !== false) {
42242
+ conditions.push("snr.message_id IS NULL");
42243
+ }
42244
+ const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
42245
+ const rows = db2.prepare(`
42246
+ SELECT
42247
+ m.id AS message_id,
42248
+ m.space,
42249
+ m.from_agent,
42250
+ m.created_at,
42251
+ m.priority,
42252
+ m.content,
42253
+ m.attachments,
42254
+ s.preview_chars,
42255
+ snr.message_id AS read_message_id
42256
+ FROM messages m
42257
+ INNER JOIN space_subscriptions s
42258
+ ON s.space = m.space
42259
+ LEFT JOIN space_notification_reads snr
42260
+ ON snr.message_id = m.id AND snr.agent = s.agent
42261
+ WHERE ${conditions.join(" AND ")}
42262
+ ORDER BY m.created_at DESC, m.id DESC
42263
+ LIMIT ${Math.max(1, Math.min(limit, 500))}
42264
+ `).all(...params);
42265
+ const notifications = rows.map((row) => ({
42266
+ message_id: row.message_id,
42267
+ space: row.space,
42268
+ from_agent: row.from_agent,
42269
+ created_at: row.created_at,
42270
+ priority: row.priority,
42271
+ preview: buildMessagePreview(row.content, row.preview_chars),
42272
+ unread: row.read_message_id == null,
42273
+ has_attachments: !!row.attachments && row.attachments !== "[]"
42274
+ }));
42275
+ if (opts.mark_read && notifications.length > 0) {
42276
+ markSpaceNotificationsRead(opts.agent, notifications.map((row) => row.message_id));
42277
+ for (const row of notifications)
42278
+ row.unread = false;
42279
+ }
42280
+ return notifications;
42281
+ }
42282
+ function markSpaceNotificationsRead(agent, messageIds) {
42283
+ if (messageIds.length === 0)
42284
+ return 0;
42285
+ const db2 = getDb();
42286
+ const insert = db2.prepare(`
42287
+ INSERT OR IGNORE INTO space_notification_reads (agent, message_id)
42288
+ VALUES (?, ?)
42289
+ `);
42290
+ let count = 0;
42291
+ for (const id of messageIds) {
42292
+ const result = insert.run(agent, id);
42293
+ count += result.changes;
42294
+ }
42295
+ return count;
42296
+ }
42297
+ function markAllSpaceNotificationsRead(agent, space) {
42298
+ const unread = readSpaceNotifications({ agent, space, unread_only: true, limit: 1e4 });
42299
+ return markSpaceNotificationsRead(agent, unread.map((row) => row.message_id));
42300
+ }
42301
+
42109
42302
  // src/mcp/tools/spaces.ts
42110
42303
  init_identity();
42111
42304
  function registerSpaceTools(server) {
@@ -42226,6 +42419,7 @@ function registerSpaceTools(server) {
42226
42419
  if (fromParam && messages.length > 0) {
42227
42420
  const agent = resolveIdentity(fromParam);
42228
42421
  recordReadReceiptsBatch(messages.map((m) => m.id), agent);
42422
+ markSpaceNotificationsRead(agent, messages.map((m) => m.id));
42229
42423
  }
42230
42424
  return {
42231
42425
  content: [{ type: "text", text: JSON.stringify(messages) }]
@@ -42265,6 +42459,86 @@ function registerSpaceTools(server) {
42265
42459
  content: [{ type: "text", text: JSON.stringify({ space, agent, left }) }]
42266
42460
  };
42267
42461
  });
42462
+ server.registerTool("subscribe_space_notifications", {
42463
+ description: "Subscribe an agent to preview-only notifications for a space.",
42464
+ inputSchema: {
42465
+ space: exports_external.string(),
42466
+ from: exports_external.string().optional(),
42467
+ preview_chars: exports_external.coerce.number().optional()
42468
+ }
42469
+ }, async (args) => {
42470
+ const agent = resolveIdentity(args.from);
42471
+ try {
42472
+ const subscription = subscribeToSpaceNotifications(args.space, agent, { preview_chars: args.preview_chars });
42473
+ return { content: [{ type: "text", text: JSON.stringify(subscription) }] };
42474
+ } catch (e) {
42475
+ return { content: [{ type: "text", text: e.message }], isError: true };
42476
+ }
42477
+ });
42478
+ server.registerTool("unsubscribe_space_notifications", {
42479
+ description: "Stop preview-only notifications for a space.",
42480
+ inputSchema: {
42481
+ space: exports_external.string(),
42482
+ from: exports_external.string().optional()
42483
+ }
42484
+ }, async (args) => {
42485
+ const agent = resolveIdentity(args.from);
42486
+ const unsubscribed = unsubscribeFromSpaceNotifications(args.space, agent);
42487
+ return { content: [{ type: "text", text: JSON.stringify({ space: args.space, agent, unsubscribed }) }] };
42488
+ });
42489
+ server.registerTool("list_space_subscriptions", {
42490
+ description: "List an agent's preview-only space notification subscriptions.",
42491
+ inputSchema: {
42492
+ from: exports_external.string().optional(),
42493
+ space: exports_external.string().optional()
42494
+ }
42495
+ }, async (args) => {
42496
+ const agent = resolveIdentity(args.from);
42497
+ const subscriptions = listSpaceNotificationSubscriptions(agent).filter((row) => !args.space || row.space === args.space);
42498
+ return { content: [{ type: "text", text: JSON.stringify(subscriptions) }] };
42499
+ });
42500
+ server.registerTool("read_space_notifications", {
42501
+ description: "Read preview-only notifications for an agent's subscribed spaces. Returns blurbs, not full message bodies.",
42502
+ inputSchema: {
42503
+ from: exports_external.string().optional(),
42504
+ space: exports_external.string().optional(),
42505
+ unread_only: exports_external.coerce.boolean().optional(),
42506
+ limit: exports_external.coerce.number().optional(),
42507
+ since: exports_external.string().optional(),
42508
+ mark_read: exports_external.coerce.boolean().optional()
42509
+ }
42510
+ }, async (args) => {
42511
+ const agent = resolveIdentity(args.from);
42512
+ const notifications = readSpaceNotifications({
42513
+ agent,
42514
+ space: args.space,
42515
+ unread_only: args.unread_only,
42516
+ limit: args.limit,
42517
+ since: args.since,
42518
+ mark_read: args.mark_read
42519
+ });
42520
+ return { content: [{ type: "text", text: JSON.stringify({ notifications, count: notifications.length }) }] };
42521
+ });
42522
+ server.registerTool("mark_space_notifications_read", {
42523
+ description: "Mark preview-only space notifications as read for an agent.",
42524
+ inputSchema: {
42525
+ from: exports_external.string().optional(),
42526
+ ids: exports_external.array(exports_external.coerce.number()).optional(),
42527
+ space: exports_external.string().optional(),
42528
+ all: exports_external.coerce.boolean().optional()
42529
+ }
42530
+ }, async (args) => {
42531
+ const agent = resolveIdentity(args.from);
42532
+ let marked = 0;
42533
+ if (args.all) {
42534
+ marked = markAllSpaceNotificationsRead(agent, args.space);
42535
+ } else if (Array.isArray(args.ids) && args.ids.length > 0) {
42536
+ marked = markSpaceNotificationsRead(agent, args.ids);
42537
+ } else {
42538
+ return { content: [{ type: "text", text: "Provide ids or all=true" }], isError: true };
42539
+ }
42540
+ return { content: [{ type: "text", text: JSON.stringify({ marked_read: marked }) }] };
42541
+ });
42268
42542
  server.registerTool("update_space", {
42269
42543
  description: "Update space description or parent.",
42270
42544
  inputSchema: {
@@ -42807,7 +43081,8 @@ function registerProjectTools(server) {
42807
43081
  init_identity();
42808
43082
 
42809
43083
  // src/mcp/channel.ts
42810
- var POLL_INTERVAL_MS = 1000;
43084
+ var DEFAULT_POLL_INTERVAL_MS = 1000;
43085
+ var DEFAULT_START_DELAY_MS = 2000;
42811
43086
  var sessionAgentId = null;
42812
43087
  var sessionClaudeId = null;
42813
43088
  function setSessionAgent(agentId, claudeSessionId) {
@@ -42828,83 +43103,126 @@ function getSessionAgent() {
42828
43103
  function getClaudeSessionId() {
42829
43104
  return sessionClaudeId || process.env.CONVERSATIONS_SESSION_ID || null;
42830
43105
  }
42831
- function registerChannelBridge(server) {
43106
+ function registerChannelBridge(server, opts) {
42832
43107
  server.server.registerCapabilities({
42833
43108
  experimental: { "claude/channel": {} }
42834
43109
  });
43110
+ const pollIntervalMs = opts?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
43111
+ const startDelayMs = opts?.startDelayMs ?? DEFAULT_START_DELAY_MS;
42835
43112
  let lastAgentMsgId = 0;
42836
43113
  let lastSessionMsgId = 0;
42837
43114
  let pollTimer = null;
43115
+ let startTimer = null;
43116
+ let polling = false;
42838
43117
  function getSessionId() {
42839
43118
  return getClaudeSessionId();
42840
43119
  }
42841
- function seedLastSeen() {
42842
- const agent = getSessionAgent();
42843
- const sid = getSessionId();
42844
- if (agent) {
42845
- const latest = readMessages({ to: agent, order: "desc", limit: 1 });
42846
- if (latest.length > 0)
42847
- lastAgentMsgId = latest[0].id;
42848
- }
42849
- if (sid) {
42850
- const latest = readMessages({ to: `session:${sid}`, order: "desc", limit: 1 });
42851
- if (latest.length > 0)
42852
- lastSessionMsgId = latest[0].id;
42853
- }
42854
- }
42855
- function pushNotification(msg, isDirect) {
42856
- if (isDirect) {
42857
- try {
42858
- markReadByIds([msg.id]);
42859
- } catch {}
42860
- }
42861
- const senderSession = msg.session_id;
42862
- const replyHint = `To reply, use conversations send_message with to="${msg.from_agent}". For direct session injection, use send_to_session with target_session_id from the sender's session.`;
42863
- const enrichedContent = `${msg.content}
43120
+ async function pushNotification(msg, mode) {
43121
+ const enrichedContent = mode === "space_blurb" ? `${msg.from_agent} posted in #${msg.space}: ${msg.content}
42864
43122
 
42865
43123
  ---
42866
- [Via Conversations from ${msg.from_agent} (msg #${msg.id}). ${replyHint}]`;
42867
- server.server.notification({
42868
- method: "notifications/claude/channel",
42869
- params: {
42870
- content: enrichedContent,
42871
- meta: {
42872
- from: msg.from_agent,
42873
- message_id: String(msg.id),
42874
- session_id: msg.session_id,
42875
- mode: isDirect ? "direct" : "dm",
42876
- ...msg.space ? { space: msg.space } : {},
42877
- ...msg.priority && msg.priority !== "normal" ? { priority: msg.priority } : {}
43124
+ [Preview only for space message #${msg.id}. To inspect the full message later, call get_message with id=${msg.id} or run conversations show ${msg.id}.]` : `${msg.content}
43125
+
43126
+ ---
43127
+ [Via Conversations from ${msg.from_agent} (msg #${msg.id}). To reply, use conversations send_message with to="${msg.from_agent}". For direct session injection, use send_to_session with target_session_id from the sender's session.]`;
43128
+ try {
43129
+ await server.server.notification({
43130
+ method: "notifications/claude/channel",
43131
+ params: {
43132
+ content: enrichedContent,
43133
+ meta: {
43134
+ from: msg.from_agent,
43135
+ message_id: String(msg.id),
43136
+ session_id: msg.session_id,
43137
+ mode,
43138
+ ...msg.space ? { space: msg.space } : {},
43139
+ ...msg.priority && msg.priority !== "normal" ? { priority: msg.priority } : {}
43140
+ }
43141
+ }
43142
+ });
43143
+ if (mode === "direct") {
43144
+ try {
43145
+ markReadByIds([msg.id]);
43146
+ } catch {}
43147
+ } else if (mode === "space_blurb") {
43148
+ const agent = getSessionAgent();
43149
+ if (agent) {
43150
+ try {
43151
+ markSpaceNotificationsRead(agent, [msg.id]);
43152
+ } catch {}
42878
43153
  }
42879
43154
  }
42880
- }).catch(() => {});
43155
+ return true;
43156
+ } catch {
43157
+ return false;
43158
+ }
42881
43159
  }
42882
- function startPolling() {
42883
- if (pollTimer)
43160
+ async function pollOnce() {
43161
+ if (polling)
42884
43162
  return;
42885
- seedLastSeen();
42886
- pollTimer = setInterval(() => {
42887
- try {
42888
- const agent = getSessionAgent();
42889
- const sid = getSessionId();
42890
- if (agent) {
42891
- const msgs = readMessages({ to: agent, order: "asc", limit: 20 }).filter((m) => m.id > lastAgentMsgId && m.from_agent !== agent);
42892
- for (const msg of msgs) {
42893
- lastAgentMsgId = msg.id;
42894
- pushNotification(msg, false);
42895
- }
43163
+ polling = true;
43164
+ try {
43165
+ const agent = getSessionAgent();
43166
+ const sid = getSessionId();
43167
+ if (agent) {
43168
+ const msgs = readMessages({ to: agent, unread_only: true, order: "asc", limit: 20 }).filter((m) => m.id > lastAgentMsgId && m.from_agent !== agent);
43169
+ for (const msg of msgs) {
43170
+ const delivered = await pushNotification(msg, "dm");
43171
+ if (!delivered)
43172
+ break;
43173
+ lastAgentMsgId = msg.id;
42896
43174
  }
42897
- if (sid) {
42898
- const msgs = readMessages({ to: `session:${sid}`, order: "asc", limit: 20 }).filter((m) => m.id > lastSessionMsgId && m.from_agent !== agent);
42899
- for (const msg of msgs) {
42900
- lastSessionMsgId = msg.id;
42901
- pushNotification(msg, true);
42902
- }
43175
+ }
43176
+ if (sid) {
43177
+ const msgs = readMessages({ to: `session:${sid}`, unread_only: true, order: "asc", limit: 20 }).filter((m) => m.id > lastSessionMsgId && m.from_agent !== agent);
43178
+ for (const msg of msgs) {
43179
+ const delivered = await pushNotification(msg, "direct");
43180
+ if (!delivered)
43181
+ break;
43182
+ lastSessionMsgId = msg.id;
42903
43183
  }
42904
- } catch {}
42905
- }, POLL_INTERVAL_MS);
43184
+ }
43185
+ if (agent) {
43186
+ const notifications = readSpaceNotifications({
43187
+ agent,
43188
+ unread_only: true,
43189
+ limit: 20,
43190
+ mark_read: false
43191
+ }).sort((left, right) => left.created_at.localeCompare(right.created_at) || left.message_id - right.message_id);
43192
+ for (const notification of notifications) {
43193
+ const delivered = await pushNotification({
43194
+ id: notification.message_id,
43195
+ content: notification.preview,
43196
+ from_agent: notification.from_agent,
43197
+ session_id: `space:${notification.space}`,
43198
+ space: notification.space,
43199
+ priority: notification.priority
43200
+ }, "space_blurb");
43201
+ if (!delivered)
43202
+ break;
43203
+ }
43204
+ }
43205
+ } finally {
43206
+ polling = false;
43207
+ }
42906
43208
  }
42907
- setTimeout(() => startPolling(), 2000);
43209
+ function startPolling() {
43210
+ if (pollTimer)
43211
+ return;
43212
+ pollTimer = setInterval(() => {
43213
+ pollOnce().catch(() => {});
43214
+ }, pollIntervalMs);
43215
+ pollOnce().catch(() => {});
43216
+ }
43217
+ startTimer = setTimeout(() => startPolling(), startDelayMs);
43218
+ return () => {
43219
+ if (startTimer)
43220
+ clearTimeout(startTimer);
43221
+ if (pollTimer)
43222
+ clearInterval(pollTimer);
43223
+ startTimer = null;
43224
+ pollTimer = null;
43225
+ };
42908
43226
  }
42909
43227
 
42910
43228
  // src/mcp/tools/agents.ts
@@ -44139,6 +44457,7 @@ function registerAdvancedTools(server, pkgVersion) {
44139
44457
  const all = [
44140
44458
  "send_message",
44141
44459
  "read_messages",
44460
+ "get_message",
44142
44461
  "read_digest",
44143
44462
  "list_sessions",
44144
44463
  "reply",
@@ -44154,6 +44473,11 @@ function registerAdvancedTools(server, pkgVersion) {
44154
44473
  "update_space",
44155
44474
  "archive_space",
44156
44475
  "unarchive_space",
44476
+ "subscribe_space_notifications",
44477
+ "unsubscribe_space_notifications",
44478
+ "list_space_subscriptions",
44479
+ "read_space_notifications",
44480
+ "mark_space_notifications_read",
44157
44481
  "create_project",
44158
44482
  "list_projects",
44159
44483
  "get_project",
@@ -44209,6 +44533,7 @@ function registerAdvancedTools(server, pkgVersion) {
44209
44533
  const descriptions = {
44210
44534
  send_message: "Send DM to agent. Required: to, content. Optional: from?, priority?(low|normal|high|urgent), blocking?",
44211
44535
  read_messages: "Read messages with filters. Optional: session_id?, from?, to?, space?, since?(ISO), limit?, unread_only?, mark_read?(default true \u2014 auto-marks returned messages as read, pass false to peek without consuming)",
44536
+ get_message: "Get the full content of a specific message by id. Required: id",
44212
44537
  read_digest: "Lightweight unread digest \u2014 preview only (no full bodies), auto-marks read, never overflows tokens. Returns { messages, total_unread, shown }. Optional: space?, session_id?, to?, since?(ISO), limit?, project_id?",
44213
44538
  list_sessions: "List all DM sessions. Optional: agent?(filter by participant)",
44214
44539
  reply: "Reply to a specific message, creating a thread (sets reply_to). Use read_thread to retrieve. Required: message_id, content. Optional: from?",
@@ -44226,6 +44551,11 @@ function registerAdvancedTools(server, pkgVersion) {
44226
44551
  update_space: "Update space fields. Required: name. Optional: description?, parent_id?(use 'null' to remove), project_id?(use 'null' to remove)",
44227
44552
  archive_space: "Archive a space (hidden from default list). Required: name",
44228
44553
  unarchive_space: "Restore archived space. Required: name",
44554
+ subscribe_space_notifications: "Subscribe to preview-only notifications for a space. Required: space. Optional: from?, preview_chars?",
44555
+ unsubscribe_space_notifications: "Stop preview-only notifications for a space. Required: space. Optional: from?",
44556
+ list_space_subscriptions: "List preview-only space notification subscriptions for the current agent. Optional: from?, space?",
44557
+ read_space_notifications: "Read preview-only notifications from subscribed spaces. Returns blurbs instead of full message bodies. Optional: from?, space?, unread_only?, since?, limit?, mark_read?",
44558
+ mark_space_notifications_read: "Mark preview-only space notifications as read. Optional: from?, ids?(array), space?, all?(bool)",
44229
44559
  create_project: "Create a project. Required: name. Optional: from?, description?, path?, repository?, tags?(JSON array), metadata?(JSON), settings?(JSON)",
44230
44560
  list_projects: "List projects. Optional: status?(active|archived)",
44231
44561
  get_project: "Get project by UUID or name. Required: id",
@@ -44572,7 +44902,7 @@ function formatError2(e) {
44572
44902
  }
44573
44903
 
44574
44904
  // src/mcp/telegram-channel.ts
44575
- var POLL_INTERVAL_MS2 = 2000;
44905
+ var POLL_INTERVAL_MS = 2000;
44576
44906
  async function telegramRequest(token, method, params) {
44577
44907
  const url2 = `https://api.telegram.org/bot${token}/${method}`;
44578
44908
  const res = await fetch(url2, {
@@ -44655,7 +44985,7 @@ ${msg.text}`;
44655
44985
  } catch {}
44656
44986
  }
44657
44987
  setTimeout(() => {
44658
- pollTimer = setInterval(() => poll(), POLL_INTERVAL_MS2);
44988
+ pollTimer = setInterval(() => poll(), POLL_INTERVAL_MS);
44659
44989
  console.error("[telegram-channel] polling started");
44660
44990
  }, 2000);
44661
44991
  }
@@ -44808,7 +45138,7 @@ function registerTmuxTools(server) {
44808
45138
  // package.json
44809
45139
  var package_default = {
44810
45140
  name: "@hasna/conversations",
44811
- version: "0.2.45",
45141
+ version: "0.2.47",
44812
45142
  description: "Real-time CLI messaging for AI agents",
44813
45143
  type: "module",
44814
45144
  bin: {
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export { sendMessage, readMessages, markRead, markSessionRead, markSpaceRead, ma
13
13
  export { listSessions, getSession, getSessionActivity, } from "./lib/sessions.js";
14
14
  export type { SessionActivity } from "./lib/sessions.js";
15
15
  export { createSpace, updateSpace, archiveSpace, unarchiveSpace, listSpaces, getSpace, joinSpace, leaveSpace, getSpaceMembers, isSpaceMember, getSpaceDepth, } from "./lib/spaces.js";
16
+ export { buildMessagePreview, subscribeToSpaceNotifications, unsubscribeFromSpaceNotifications, listSpaceNotificationSubscriptions, getSubscribedSpaces, readSpaceNotifications, markSpaceNotificationsRead, markAllSpaceNotificationsRead, } from "./lib/space-notifications.js";
16
17
  export { createProject, listProjects, getProject, getProjectByName, updateProject, deleteProject, } from "./lib/projects.js";
17
18
  export { getDb, getDbPath, closeDb, } from "./lib/db.js";
18
19
  export { startPolling, useMessages, useSpaceMessages, } from "./lib/poll.js";