@hasna/conversations 0.2.2 → 0.2.4

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 CHANGED
@@ -2372,7 +2372,15 @@ function readMessages(opts = {}) {
2372
2372
  const resolvedLimit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
2373
2373
  const order = opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
2374
2374
  const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} LIMIT ${resolvedLimit}`).all(...params);
2375
- const messages = rows.map(parseMessage);
2375
+ let messages = rows.map(parseMessage);
2376
+ if (opts.max_content_length && opts.max_content_length > 0) {
2377
+ messages = messages.map((m) => {
2378
+ if (m.content.length > opts.max_content_length) {
2379
+ return { ...m, content: m.content.slice(0, opts.max_content_length) + "\u2026", truncated: true };
2380
+ }
2381
+ return m;
2382
+ });
2383
+ }
2376
2384
  if (opts.compact)
2377
2385
  return messages.map(compactMessage);
2378
2386
  return messages;
@@ -2644,6 +2652,39 @@ function searchMessages(opts) {
2644
2652
  return { ...msg, snippet: null, relevance_score: 0 };
2645
2653
  });
2646
2654
  }
2655
+ function listUnreadCounts(agent) {
2656
+ const db2 = getDb();
2657
+ if (agent) {
2658
+ const rows2 = db2.prepare(`
2659
+ SELECT
2660
+ space,
2661
+ COUNT(CASE WHEN read_at IS NULL AND (to_agent = ? OR to_agent IS NULL OR to_agent = '') THEN 1 END) AS unread_count,
2662
+ MAX(created_at) AS latest_message_at
2663
+ FROM messages
2664
+ WHERE space IN (
2665
+ SELECT DISTINCT space FROM space_members WHERE agent = ?
2666
+ UNION
2667
+ SELECT DISTINCT space FROM messages WHERE to_agent = ? AND space IS NOT NULL
2668
+ )
2669
+ GROUP BY space
2670
+ HAVING COUNT(*) > 0
2671
+ ORDER BY unread_count DESC, latest_message_at DESC
2672
+ `).all(agent, agent, agent);
2673
+ return rows2;
2674
+ }
2675
+ const rows = db2.prepare(`
2676
+ SELECT
2677
+ space,
2678
+ COUNT(CASE WHEN read_at IS NULL THEN 1 END) AS unread_count,
2679
+ MAX(created_at) AS latest_message_at
2680
+ FROM messages
2681
+ WHERE space IS NOT NULL
2682
+ GROUP BY space
2683
+ HAVING COUNT(*) > 0
2684
+ ORDER BY unread_count DESC, latest_message_at DESC
2685
+ `).all();
2686
+ return rows;
2687
+ }
2647
2688
  var init_messages = __esm(() => {
2648
2689
  init_db();
2649
2690
  init_webhooks();
@@ -4283,7 +4324,7 @@ var init_poll = __esm(() => {
4283
4324
  var require_package = __commonJS((exports, module) => {
4284
4325
  module.exports = {
4285
4326
  name: "@hasna/conversations",
4286
- version: "0.2.2",
4327
+ version: "0.2.4",
4287
4328
  description: "Real-time CLI messaging for AI agents",
4288
4329
  type: "module",
4289
4330
  bin: {
@@ -33535,7 +33576,8 @@ var init_mcp2 = __esm(() => {
33535
33576
  since: exports_external.string().optional(),
33536
33577
  limit: exports_external.coerce.number().optional(),
33537
33578
  unread_only: exports_external.coerce.boolean().optional(),
33538
- mark_read: exports_external.coerce.boolean().optional()
33579
+ mark_read: exports_external.coerce.boolean().optional(),
33580
+ max_content_length: exports_external.coerce.number().optional().describe("Truncate each message content to N chars (adds truncated:true flag)")
33539
33581
  }
33540
33582
  }, async (args) => {
33541
33583
  const agent = resolveIdentity(args.from);
@@ -33704,6 +33746,17 @@ var init_mcp2 = __esm(() => {
33704
33746
  content: [{ type: "text", text: JSON.stringify(spaces) }]
33705
33747
  };
33706
33748
  });
33749
+ server.registerTool("list_unread_counts", {
33750
+ description: "Get unread message counts per space without fetching message content. Use this at session start to triage which spaces need attention before calling read_messages.",
33751
+ inputSchema: {
33752
+ agent: exports_external.string().optional().describe("Filter to spaces the agent is a member of or has received messages in. Omit for global unread counts.")
33753
+ }
33754
+ }, async (args) => {
33755
+ const counts = listUnreadCounts(args.agent);
33756
+ return {
33757
+ content: [{ type: "text", text: JSON.stringify(counts) }]
33758
+ };
33759
+ });
33707
33760
  server.registerTool("send_to_space", {
33708
33761
  description: "Post a message to a space.",
33709
33762
  inputSchema: {
@@ -33742,11 +33795,12 @@ var init_mcp2 = __esm(() => {
33742
33795
  space: exports_external.string(),
33743
33796
  since: exports_external.string().optional(),
33744
33797
  limit: exports_external.coerce.number().optional(),
33745
- mark_read: exports_external.coerce.boolean().optional()
33798
+ mark_read: exports_external.coerce.boolean().optional(),
33799
+ max_content_length: exports_external.coerce.number().optional().describe("Truncate each message content to N chars (adds truncated:true flag)")
33746
33800
  }
33747
33801
  }, async (args) => {
33748
- const { space, since, limit, mark_read } = args;
33749
- const messages = readMessages({ space, since, limit });
33802
+ const { space, since, limit, mark_read, max_content_length } = args;
33803
+ const messages = readMessages({ space, since, limit, max_content_length });
33750
33804
  if (mark_read !== false && messages.length > 0) {
33751
33805
  markReadByIds(messages.map((m) => m.id));
33752
33806
  }
@@ -34656,6 +34710,7 @@ var init_mcp2 = __esm(() => {
34656
34710
  search_messages: "Full-text search messages. Required: query. Optional: space?, from?, to?, limit?",
34657
34711
  export_messages: "Export messages as JSON or CSV. Optional: space?, session_id?, from?, since?, until?, format?(json|csv)",
34658
34712
  create_space: "Create space and auto-join. Required: name. Optional: from?, description?, parent_id?(max 3 levels), project_id?",
34713
+ list_unread_counts: "Get unread message counts per space (no content). Ideal for session start triage. Optional: agent?(filter to agent's spaces)",
34659
34714
  list_spaces: "List spaces with member/message counts. Optional: project_id?, parent_id?(use 'null' for top-level), include_archived?",
34660
34715
  send_to_space: "Post message to space. Required: space, content. Optional: from?, priority?(low|normal|high|urgent), blocking?",
34661
34716
  read_space: "Read messages in a space. Required: space. Optional: since?(ISO), limit?, mark_read?(default true \u2014 auto-marks returned messages as read)",
package/bin/mcp.js CHANGED
@@ -28881,7 +28881,15 @@ function readMessages(opts = {}) {
28881
28881
  const resolvedLimit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
28882
28882
  const order = opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
28883
28883
  const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} LIMIT ${resolvedLimit}`).all(...params);
28884
- const messages = rows.map(parseMessage);
28884
+ let messages = rows.map(parseMessage);
28885
+ if (opts.max_content_length && opts.max_content_length > 0) {
28886
+ messages = messages.map((m) => {
28887
+ if (m.content.length > opts.max_content_length) {
28888
+ return { ...m, content: m.content.slice(0, opts.max_content_length) + "\u2026", truncated: true };
28889
+ }
28890
+ return m;
28891
+ });
28892
+ }
28885
28893
  if (opts.compact)
28886
28894
  return messages.map(compactMessage);
28887
28895
  return messages;
@@ -29141,6 +29149,39 @@ function searchMessages(opts) {
29141
29149
  return { ...msg, snippet: null, relevance_score: 0 };
29142
29150
  });
29143
29151
  }
29152
+ function listUnreadCounts(agent) {
29153
+ const db2 = getDb();
29154
+ if (agent) {
29155
+ const rows2 = db2.prepare(`
29156
+ SELECT
29157
+ space,
29158
+ COUNT(CASE WHEN read_at IS NULL AND (to_agent = ? OR to_agent IS NULL OR to_agent = '') THEN 1 END) AS unread_count,
29159
+ MAX(created_at) AS latest_message_at
29160
+ FROM messages
29161
+ WHERE space IN (
29162
+ SELECT DISTINCT space FROM space_members WHERE agent = ?
29163
+ UNION
29164
+ SELECT DISTINCT space FROM messages WHERE to_agent = ? AND space IS NOT NULL
29165
+ )
29166
+ GROUP BY space
29167
+ HAVING COUNT(*) > 0
29168
+ ORDER BY unread_count DESC, latest_message_at DESC
29169
+ `).all(agent, agent, agent);
29170
+ return rows2;
29171
+ }
29172
+ const rows = db2.prepare(`
29173
+ SELECT
29174
+ space,
29175
+ COUNT(CASE WHEN read_at IS NULL THEN 1 END) AS unread_count,
29176
+ MAX(created_at) AS latest_message_at
29177
+ FROM messages
29178
+ WHERE space IS NOT NULL
29179
+ GROUP BY space
29180
+ HAVING COUNT(*) > 0
29181
+ ORDER BY unread_count DESC, latest_message_at DESC
29182
+ `).all();
29183
+ return rows;
29184
+ }
29144
29185
 
29145
29186
  // src/lib/sessions.ts
29146
29187
  init_db();
@@ -30767,7 +30808,7 @@ function getGraphStats() {
30767
30808
  // package.json
30768
30809
  var package_default = {
30769
30810
  name: "@hasna/conversations",
30770
- version: "0.2.2",
30811
+ version: "0.2.4",
30771
30812
  description: "Real-time CLI messaging for AI agents",
30772
30813
  type: "module",
30773
30814
  bin: {
@@ -30898,7 +30939,8 @@ server.registerTool("read_messages", {
30898
30939
  since: exports_external.string().optional(),
30899
30940
  limit: exports_external.coerce.number().optional(),
30900
30941
  unread_only: exports_external.coerce.boolean().optional(),
30901
- mark_read: exports_external.coerce.boolean().optional()
30942
+ mark_read: exports_external.coerce.boolean().optional(),
30943
+ max_content_length: exports_external.coerce.number().optional().describe("Truncate each message content to N chars (adds truncated:true flag)")
30902
30944
  }
30903
30945
  }, async (args) => {
30904
30946
  const agent = resolveIdentity(args.from);
@@ -31067,6 +31109,17 @@ server.registerTool("list_spaces", {
31067
31109
  content: [{ type: "text", text: JSON.stringify(spaces) }]
31068
31110
  };
31069
31111
  });
31112
+ server.registerTool("list_unread_counts", {
31113
+ description: "Get unread message counts per space without fetching message content. Use this at session start to triage which spaces need attention before calling read_messages.",
31114
+ inputSchema: {
31115
+ agent: exports_external.string().optional().describe("Filter to spaces the agent is a member of or has received messages in. Omit for global unread counts.")
31116
+ }
31117
+ }, async (args) => {
31118
+ const counts = listUnreadCounts(args.agent);
31119
+ return {
31120
+ content: [{ type: "text", text: JSON.stringify(counts) }]
31121
+ };
31122
+ });
31070
31123
  server.registerTool("send_to_space", {
31071
31124
  description: "Post a message to a space.",
31072
31125
  inputSchema: {
@@ -31105,11 +31158,12 @@ server.registerTool("read_space", {
31105
31158
  space: exports_external.string(),
31106
31159
  since: exports_external.string().optional(),
31107
31160
  limit: exports_external.coerce.number().optional(),
31108
- mark_read: exports_external.coerce.boolean().optional()
31161
+ mark_read: exports_external.coerce.boolean().optional(),
31162
+ max_content_length: exports_external.coerce.number().optional().describe("Truncate each message content to N chars (adds truncated:true flag)")
31109
31163
  }
31110
31164
  }, async (args) => {
31111
- const { space, since, limit, mark_read } = args;
31112
- const messages = readMessages({ space, since, limit });
31165
+ const { space, since, limit, mark_read, max_content_length } = args;
31166
+ const messages = readMessages({ space, since, limit, max_content_length });
31113
31167
  if (mark_read !== false && messages.length > 0) {
31114
31168
  markReadByIds(messages.map((m) => m.id));
31115
31169
  }
@@ -32019,6 +32073,7 @@ server.registerTool("describe_tools", {
32019
32073
  search_messages: "Full-text search messages. Required: query. Optional: space?, from?, to?, limit?",
32020
32074
  export_messages: "Export messages as JSON or CSV. Optional: space?, session_id?, from?, since?, until?, format?(json|csv)",
32021
32075
  create_space: "Create space and auto-join. Required: name. Optional: from?, description?, parent_id?(max 3 levels), project_id?",
32076
+ list_unread_counts: "Get unread message counts per space (no content). Ideal for session start triage. Optional: agent?(filter to agent's spaces)",
32022
32077
  list_spaces: "List spaces with member/message counts. Optional: project_id?, parent_id?(use 'null' for top-level), include_archived?",
32023
32078
  send_to_space: "Post message to space. Required: space, content. Optional: from?, priority?(low|normal|high|urgent), blocking?",
32024
32079
  read_space: "Read messages in a space. Required: space. Optional: since?(ISO), limit?, mark_read?(default true \u2014 auto-marks returned messages as read)",
package/dist/index.js CHANGED
@@ -2346,7 +2346,15 @@ function readMessages(opts = {}) {
2346
2346
  const resolvedLimit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
2347
2347
  const order = opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
2348
2348
  const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} LIMIT ${resolvedLimit}`).all(...params);
2349
- const messages = rows.map(parseMessage);
2349
+ let messages = rows.map(parseMessage);
2350
+ if (opts.max_content_length && opts.max_content_length > 0) {
2351
+ messages = messages.map((m) => {
2352
+ if (m.content.length > opts.max_content_length) {
2353
+ return { ...m, content: m.content.slice(0, opts.max_content_length) + "\u2026", truncated: true };
2354
+ }
2355
+ return m;
2356
+ });
2357
+ }
2350
2358
  if (opts.compact)
2351
2359
  return messages.map(compactMessage);
2352
2360
  return messages;
@@ -56,3 +56,14 @@ export declare function getPinnedMessages(opts?: {
56
56
  export declare function getUnreadBlockers(agent: string): Message[];
57
57
  export declare function getThreadReplies(messageId: number): Message[];
58
58
  export declare function searchMessages(opts: SearchMessagesOptions): SearchResult[];
59
+ export interface UnreadCount {
60
+ space: string;
61
+ unread_count: number;
62
+ latest_message_at: string | null;
63
+ }
64
+ /**
65
+ * Get unread message counts per space — lightweight alternative to read_messages.
66
+ * Returns only spaces where the agent is a member (via space_members) or has received messages.
67
+ * If agent is omitted, returns counts for all spaces.
68
+ */
69
+ export declare function listUnreadCounts(agent?: string): UnreadCount[];
package/dist/types.d.ts CHANGED
@@ -19,6 +19,7 @@ export interface Message {
19
19
  blocking: boolean;
20
20
  attachments: Attachment[] | null;
21
21
  reply_to: number | null;
22
+ truncated?: boolean;
22
23
  }
23
24
  export interface Reaction {
24
25
  id: number;
@@ -106,6 +107,7 @@ export interface ReadMessagesOptions {
106
107
  unread_only?: boolean;
107
108
  order?: "asc" | "desc";
108
109
  compact?: boolean;
110
+ max_content_length?: number;
109
111
  }
110
112
  export interface SearchMessagesOptions {
111
113
  query: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/conversations",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Real-time CLI messaging for AI agents",
5
5
  "type": "module",
6
6
  "bin": {