@hasna/conversations 0.1.17 → 0.1.19

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
@@ -6606,6 +6606,17 @@ function getDb() {
6606
6606
  metadata TEXT
6607
6607
  )
6608
6608
  `);
6609
+ db.exec(`
6610
+ CREATE TABLE IF NOT EXISTS reactions (
6611
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
6612
+ message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
6613
+ agent TEXT NOT NULL,
6614
+ emoji TEXT NOT NULL,
6615
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
6616
+ UNIQUE(message_id, agent, emoji)
6617
+ )
6618
+ `);
6619
+ db.exec("CREATE INDEX IF NOT EXISTS idx_reactions_message ON reactions(message_id)");
6609
6620
  const existingTables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();
6610
6621
  const tableNames = existingTables.map((t) => t.name);
6611
6622
  if (tableNames.includes("channels") && tableNames.includes("spaces")) {
@@ -6663,6 +6674,46 @@ function getDb() {
6663
6674
  db.exec("ALTER TABLE messages ADD COLUMN blocking INTEGER NOT NULL DEFAULT 0");
6664
6675
  db.exec("CREATE INDEX IF NOT EXISTS idx_messages_blocking ON messages(blocking)");
6665
6676
  }
6677
+ if (!colNames2.includes("attachments")) {
6678
+ db.exec("ALTER TABLE messages ADD COLUMN attachments TEXT");
6679
+ }
6680
+ if (!colNames2.includes("reply_to")) {
6681
+ db.exec("ALTER TABLE messages ADD COLUMN reply_to INTEGER REFERENCES messages(id)");
6682
+ db.exec("CREATE INDEX IF NOT EXISTS idx_messages_reply_to ON messages(reply_to)");
6683
+ }
6684
+ const ftsExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='messages_fts'").get();
6685
+ if (!ftsExists) {
6686
+ db.exec(`
6687
+ CREATE VIRTUAL TABLE messages_fts USING fts5(
6688
+ content, from_agent, to_agent, space,
6689
+ content_rowid='id', content='messages'
6690
+ )
6691
+ `);
6692
+ db.exec(`
6693
+ INSERT INTO messages_fts(rowid, content, from_agent, to_agent, space)
6694
+ SELECT id, content, from_agent, to_agent, space FROM messages
6695
+ `);
6696
+ db.exec(`
6697
+ CREATE TRIGGER IF NOT EXISTS messages_fts_insert AFTER INSERT ON messages BEGIN
6698
+ INSERT INTO messages_fts(rowid, content, from_agent, to_agent, space)
6699
+ VALUES (new.id, new.content, new.from_agent, new.to_agent, new.space);
6700
+ END
6701
+ `);
6702
+ db.exec(`
6703
+ CREATE TRIGGER IF NOT EXISTS messages_fts_delete AFTER DELETE ON messages BEGIN
6704
+ INSERT INTO messages_fts(messages_fts, rowid, content, from_agent, to_agent, space)
6705
+ VALUES ('delete', old.id, old.content, old.from_agent, old.to_agent, old.space);
6706
+ END
6707
+ `);
6708
+ db.exec(`
6709
+ CREATE TRIGGER IF NOT EXISTS messages_fts_update AFTER UPDATE OF content ON messages BEGIN
6710
+ INSERT INTO messages_fts(messages_fts, rowid, content, from_agent, to_agent, space)
6711
+ VALUES ('delete', old.id, old.content, old.from_agent, old.to_agent, old.space);
6712
+ INSERT INTO messages_fts(rowid, content, from_agent, to_agent, space)
6713
+ VALUES (new.id, new.content, new.from_agent, new.to_agent, new.space);
6714
+ END
6715
+ `);
6716
+ }
6666
6717
  return db;
6667
6718
  }
6668
6719
  function closeDb() {
@@ -28494,6 +28545,85 @@ class StdioServerTransport {
28494
28545
  // src/lib/messages.ts
28495
28546
  init_db();
28496
28547
  import { randomUUID } from "crypto";
28548
+ import { mkdirSync as mkdirSync2, copyFileSync, statSync } from "fs";
28549
+ import { join as join3 } from "path";
28550
+ import { homedir as homedir3 } from "os";
28551
+
28552
+ // src/lib/webhooks.ts
28553
+ import { readFileSync } from "fs";
28554
+ import { join as join2 } from "path";
28555
+ import { homedir as homedir2 } from "os";
28556
+ var cachedConfig = null;
28557
+ var configLoadedAt = 0;
28558
+ var CONFIG_CACHE_MS = 1e4;
28559
+ function getConfigPath() {
28560
+ return process.env.CONVERSATIONS_CONFIG_PATH || join2(homedir2(), ".conversations", "config.json");
28561
+ }
28562
+ function loadConfig() {
28563
+ const now = Date.now();
28564
+ if (cachedConfig && now - configLoadedAt < CONFIG_CACHE_MS)
28565
+ return cachedConfig;
28566
+ try {
28567
+ const raw = readFileSync(getConfigPath(), "utf-8");
28568
+ cachedConfig = JSON.parse(raw);
28569
+ configLoadedAt = now;
28570
+ return cachedConfig;
28571
+ } catch {
28572
+ cachedConfig = {};
28573
+ configLoadedAt = now;
28574
+ return cachedConfig;
28575
+ }
28576
+ }
28577
+ function matchesEvent(webhook, msg) {
28578
+ for (const event of webhook.events) {
28579
+ if (event === "dm" && !msg.space)
28580
+ return true;
28581
+ if (event === "blocker" && msg.blocking)
28582
+ return true;
28583
+ if (event === "space" && msg.space)
28584
+ return true;
28585
+ if (event === "mention" && webhook.agent && msg.content.includes(`@${webhook.agent}`))
28586
+ return true;
28587
+ }
28588
+ return false;
28589
+ }
28590
+ function fireWebhooks(msg) {
28591
+ const config2 = loadConfig();
28592
+ if (!config2.webhooks || config2.webhooks.length === 0)
28593
+ return;
28594
+ for (const webhook of config2.webhooks) {
28595
+ if (webhook.agent && msg.to_agent !== webhook.agent && !msg.space)
28596
+ continue;
28597
+ if (!matchesEvent(webhook, msg))
28598
+ continue;
28599
+ fetch(webhook.url, {
28600
+ method: "POST",
28601
+ headers: { "Content-Type": "application/json" },
28602
+ body: JSON.stringify({
28603
+ id: msg.id,
28604
+ from: msg.from_agent,
28605
+ to: msg.to_agent,
28606
+ space: msg.space,
28607
+ content: msg.content,
28608
+ priority: msg.priority,
28609
+ blocking: msg.blocking,
28610
+ created_at: msg.created_at
28611
+ })
28612
+ }).catch(() => {});
28613
+ }
28614
+ }
28615
+
28616
+ // src/lib/messages.ts
28617
+ function compactMessage(msg) {
28618
+ const result = {};
28619
+ for (const key of Object.keys(msg)) {
28620
+ const val = msg[key];
28621
+ if (val !== null && val !== undefined) {
28622
+ result[key] = val;
28623
+ }
28624
+ }
28625
+ return result;
28626
+ }
28497
28627
  function parseMessage(row) {
28498
28628
  let metadata = null;
28499
28629
  if (row.metadata) {
@@ -28503,11 +28633,53 @@ function parseMessage(row) {
28503
28633
  metadata = null;
28504
28634
  }
28505
28635
  }
28636
+ let attachments = null;
28637
+ if (row.attachments) {
28638
+ try {
28639
+ attachments = JSON.parse(row.attachments);
28640
+ } catch {
28641
+ attachments = null;
28642
+ }
28643
+ }
28506
28644
  return {
28507
28645
  ...row,
28508
28646
  metadata,
28509
- blocking: !!row.blocking
28510
- };
28647
+ attachments,
28648
+ blocking: !!row.blocking,
28649
+ reply_to: row.reply_to || null
28650
+ };
28651
+ }
28652
+ function getAttachmentsDir() {
28653
+ if (process.env.CONVERSATIONS_ATTACHMENTS_DIR)
28654
+ return process.env.CONVERSATIONS_ATTACHMENTS_DIR;
28655
+ return join3(homedir3(), ".conversations", "attachments");
28656
+ }
28657
+ function guessMimeType(name) {
28658
+ const ext = name.split(".").pop()?.toLowerCase();
28659
+ const mimeMap = {
28660
+ txt: "text/plain",
28661
+ md: "text/markdown",
28662
+ json: "application/json",
28663
+ js: "text/javascript",
28664
+ ts: "text/typescript",
28665
+ py: "text/x-python",
28666
+ html: "text/html",
28667
+ css: "text/css",
28668
+ xml: "application/xml",
28669
+ png: "image/png",
28670
+ jpg: "image/jpeg",
28671
+ jpeg: "image/jpeg",
28672
+ gif: "image/gif",
28673
+ svg: "image/svg+xml",
28674
+ webp: "image/webp",
28675
+ pdf: "application/pdf",
28676
+ zip: "application/zip",
28677
+ gz: "application/gzip",
28678
+ csv: "text/csv",
28679
+ yaml: "text/yaml",
28680
+ yml: "text/yaml"
28681
+ };
28682
+ return mimeMap[ext || ""] || "application/octet-stream";
28511
28683
  }
28512
28684
  function sendMessage(opts) {
28513
28685
  const db2 = getDb();
@@ -28516,13 +28688,35 @@ function sendMessage(opts) {
28516
28688
  const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
28517
28689
  const normalizedPriority = opts.priority === "low" || opts.priority === "normal" || opts.priority === "high" || opts.priority === "urgent" ? opts.priority : "normal";
28518
28690
  const blocking = opts.blocking ? 1 : 0;
28691
+ const replyTo = opts.reply_to || null;
28519
28692
  const stmt = db2.prepare(`
28520
- INSERT INTO messages (session_id, from_agent, to_agent, space, content, priority, working_dir, repository, branch, metadata, blocking)
28521
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
28693
+ INSERT INTO messages (session_id, from_agent, to_agent, space, content, priority, working_dir, repository, branch, metadata, blocking, reply_to)
28694
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
28522
28695
  RETURNING *
28523
28696
  `);
28524
- const row = stmt.get(sessionId, opts.from, opts.to, opts.space || null, opts.content, normalizedPriority, opts.working_dir || null, opts.repository || null, opts.branch || null, metadata, blocking);
28525
- return parseMessage(row);
28697
+ const row = stmt.get(sessionId, opts.from, opts.to, opts.space || null, opts.content, normalizedPriority, opts.working_dir || null, opts.repository || null, opts.branch || null, metadata, blocking, replyTo);
28698
+ const message = parseMessage(row);
28699
+ if (opts.attachments && opts.attachments.length > 0) {
28700
+ const attachmentsDir = join3(getAttachmentsDir(), String(message.id));
28701
+ mkdirSync2(attachmentsDir, { recursive: true });
28702
+ const attachmentInfos = [];
28703
+ for (const att of opts.attachments) {
28704
+ const destPath = join3(attachmentsDir, att.name);
28705
+ copyFileSync(att.source_path, destPath);
28706
+ const stat = statSync(destPath);
28707
+ attachmentInfos.push({
28708
+ name: att.name,
28709
+ path: destPath,
28710
+ size: stat.size,
28711
+ mime_type: guessMimeType(att.name)
28712
+ });
28713
+ }
28714
+ const attachmentsJson = JSON.stringify(attachmentInfos);
28715
+ db2.prepare("UPDATE messages SET attachments = ? WHERE id = ?").run(attachmentsJson, message.id);
28716
+ message.attachments = attachmentInfos;
28717
+ }
28718
+ fireWebhooks(message);
28719
+ return message;
28526
28720
  }
28527
28721
  function readMessages(opts = {}) {
28528
28722
  const db2 = getDb();
@@ -28556,10 +28750,13 @@ function readMessages(opts = {}) {
28556
28750
  conditions.push("read_at IS NULL");
28557
28751
  }
28558
28752
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
28559
- const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? `LIMIT ${Math.floor(opts.limit)}` : "";
28753
+ const resolvedLimit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
28560
28754
  const order = opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
28561
- const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} ${limit}`).all(...params);
28562
- return rows.map(parseMessage);
28755
+ const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} LIMIT ${resolvedLimit}`).all(...params);
28756
+ const messages = rows.map(parseMessage);
28757
+ if (opts.compact)
28758
+ return messages.map(compactMessage);
28759
+ return messages;
28563
28760
  }
28564
28761
  function markRead(ids, reader) {
28565
28762
  const db2 = getDb();
@@ -28693,6 +28890,33 @@ function getUnreadBlockers(agent) {
28693
28890
  }
28694
28891
  function searchMessages(opts) {
28695
28892
  const db2 = getDb();
28893
+ const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
28894
+ try {
28895
+ const ftsConditions = [];
28896
+ const ftsParams = [];
28897
+ const words = opts.query.trim().split(/\s+/).filter(Boolean);
28898
+ const ftsQuery = words.map((w) => `"${w.replace(/"/g, '""')}"`).join(" ");
28899
+ ftsConditions.push("messages_fts MATCH ?");
28900
+ ftsParams.push(ftsQuery);
28901
+ let extraWhere = "";
28902
+ if (opts.space) {
28903
+ extraWhere += " AND m.space = ?";
28904
+ ftsParams.push(opts.space);
28905
+ }
28906
+ if (opts.from) {
28907
+ extraWhere += " AND m.from_agent = ?";
28908
+ ftsParams.push(opts.from);
28909
+ }
28910
+ if (opts.to) {
28911
+ extraWhere += " AND m.to_agent = ?";
28912
+ ftsParams.push(opts.to);
28913
+ }
28914
+ const rows2 = db2.prepare(`SELECT m.* FROM messages m
28915
+ JOIN messages_fts ON messages_fts.rowid = m.id
28916
+ WHERE ${ftsConditions.join(" AND ")}${extraWhere}
28917
+ ORDER BY m.created_at DESC, m.id DESC LIMIT ${limit}`).all(...ftsParams);
28918
+ return rows2.map(parseMessage);
28919
+ } catch {}
28696
28920
  const conditions = ["content LIKE ?"];
28697
28921
  const params = [`%${opts.query}%`];
28698
28922
  if (opts.space) {
@@ -28707,7 +28931,6 @@ function searchMessages(opts) {
28707
28931
  conditions.push("to_agent = ?");
28708
28932
  params.push(opts.to);
28709
28933
  }
28710
- const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 50;
28711
28934
  const where = `WHERE ${conditions.join(" AND ")}`;
28712
28935
  const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at DESC, id DESC LIMIT ${limit}`).all(...params);
28713
28936
  return rows.map(parseMessage);
@@ -29085,9 +29308,9 @@ function deleteProject(id) {
29085
29308
  }
29086
29309
 
29087
29310
  // src/lib/identity.ts
29088
- import { readFileSync, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
29089
- import { join as join2, dirname as dirname2 } from "path";
29090
- import { homedir as homedir2 } from "os";
29311
+ import { readFileSync as readFileSync2, writeFileSync, mkdirSync as mkdirSync3 } from "fs";
29312
+ import { join as join4, dirname as dirname2 } from "path";
29313
+ import { homedir as homedir4 } from "os";
29091
29314
 
29092
29315
  // src/lib/names.ts
29093
29316
  var AGENT_NAMES = [
@@ -29439,7 +29662,7 @@ var AGENT_NAMES = [
29439
29662
  ];
29440
29663
 
29441
29664
  // src/lib/identity.ts
29442
- var AGENT_ID_FILE = join2(homedir2(), ".conversations", "agent-id");
29665
+ var AGENT_ID_FILE = join4(homedir4(), ".conversations", "agent-id");
29443
29666
  var cachedAutoName = null;
29444
29667
  function isNameTaken(name) {
29445
29668
  try {
@@ -29455,7 +29678,7 @@ function getAutoName() {
29455
29678
  if (cachedAutoName)
29456
29679
  return cachedAutoName;
29457
29680
  try {
29458
- const name2 = readFileSync(AGENT_ID_FILE, "utf-8").trim();
29681
+ const name2 = readFileSync2(AGENT_ID_FILE, "utf-8").trim();
29459
29682
  if (name2) {
29460
29683
  cachedAutoName = name2;
29461
29684
  return name2;
@@ -29471,7 +29694,7 @@ function getAutoName() {
29471
29694
  }
29472
29695
  cachedAutoName = name;
29473
29696
  try {
29474
- mkdirSync2(dirname2(AGENT_ID_FILE), { recursive: true });
29697
+ mkdirSync3(dirname2(AGENT_ID_FILE), { recursive: true });
29475
29698
  writeFileSync(AGENT_ID_FILE, name + `
29476
29699
  `, "utf-8");
29477
29700
  } catch {}
@@ -29554,7 +29777,7 @@ function renameAgent(oldName, newName) {
29554
29777
  // package.json
29555
29778
  var package_default = {
29556
29779
  name: "@hasna/conversations",
29557
- version: "0.1.17",
29780
+ version: "0.1.19",
29558
29781
  description: "Real-time CLI messaging for AI agents",
29559
29782
  type: "module",
29560
29783
  bin: {
@@ -29638,9 +29861,9 @@ var server = new McpServer({
29638
29861
  });
29639
29862
  server.registerTool("send_message", {
29640
29863
  title: "Send Message",
29641
- description: "Send a direct message to another agent. Pass 'from' to identify yourself, or it falls back to CONVERSATIONS_AGENT_ID env var.",
29864
+ description: "Send a direct message to another agent.",
29642
29865
  inputSchema: {
29643
- from: exports_external.string().optional().describe("Your agent ID (e.g. 'claude-1', 'assistant'). Falls back to CONVERSATIONS_AGENT_ID env var."),
29866
+ from: exports_external.string().optional().describe("Your agent ID. Falls back to CONVERSATIONS_AGENT_ID env var."),
29644
29867
  to: exports_external.string().describe("Recipient agent ID"),
29645
29868
  content: exports_external.string().describe("Message content"),
29646
29869
  session_id: exports_external.string().optional().describe("Session ID (auto-generated if omitted)"),
@@ -29649,7 +29872,7 @@ server.registerTool("send_message", {
29649
29872
  repository: exports_external.string().optional().describe("Repository context"),
29650
29873
  branch: exports_external.string().optional().describe("Branch context"),
29651
29874
  metadata: exports_external.string().optional().describe("JSON metadata string"),
29652
- blocking: exports_external.boolean().optional().describe("Send as a blocking message. Recipients must acknowledge before continuing.")
29875
+ blocking: exports_external.boolean().optional().describe("Blocking message \u2014 recipients must acknowledge before continuing")
29653
29876
  }
29654
29877
  }, async ({ from: fromParam, to, content, session_id, priority, working_dir, repository, branch, metadata, blocking }) => {
29655
29878
  const from = resolveIdentity(fromParam);
@@ -29659,7 +29882,7 @@ server.registerTool("send_message", {
29659
29882
  parsedMetadata = JSON.parse(metadata);
29660
29883
  } catch {
29661
29884
  return {
29662
- content: [{ type: "text", text: "Invalid metadata JSON." }],
29885
+ content: [{ type: "text", text: "invalid JSON" }],
29663
29886
  isError: true
29664
29887
  };
29665
29888
  }
@@ -29685,12 +29908,12 @@ server.registerTool("read_messages", {
29685
29908
  description: "Read messages with optional filters. Returns messages sorted by time.",
29686
29909
  inputSchema: {
29687
29910
  session_id: exports_external.string().optional().describe("Filter by session ID"),
29688
- from: exports_external.string().optional().describe("Filter by sender agent ID"),
29689
- to: exports_external.string().optional().describe("Filter by recipient agent ID"),
29911
+ from: exports_external.string().optional().describe("Filter by sender"),
29912
+ to: exports_external.string().optional().describe("Filter by recipient"),
29690
29913
  space: exports_external.string().optional().describe("Filter by space name"),
29691
- since: exports_external.string().optional().describe("Messages after this ISO timestamp"),
29692
- limit: exports_external.number().optional().describe("Max messages to return"),
29693
- unread_only: exports_external.boolean().optional().describe("Only return unread messages")
29914
+ since: exports_external.string().optional().describe("ISO timestamp lower bound"),
29915
+ limit: exports_external.number().optional().describe("Max messages to return (default 20)"),
29916
+ unread_only: exports_external.boolean().optional().describe("Only unread messages")
29694
29917
  }
29695
29918
  }, async (opts) => {
29696
29919
  const messages = readMessages(opts);
@@ -29712,7 +29935,7 @@ server.registerTool("list_sessions", {
29712
29935
  });
29713
29936
  server.registerTool("reply", {
29714
29937
  title: "Reply to Message",
29715
- description: "Reply to a message by its ID. Automatically uses the same session and sends to the original sender.",
29938
+ description: "Reply to a message by ID. Uses the same session and sends to the original sender.",
29716
29939
  inputSchema: {
29717
29940
  from: exports_external.string().optional().describe("Your agent ID. Falls back to CONVERSATIONS_AGENT_ID env var."),
29718
29941
  message_id: exports_external.number().describe("ID of the message to reply to"),
@@ -29744,7 +29967,7 @@ server.registerTool("reply", {
29744
29967
  });
29745
29968
  server.registerTool("mark_read", {
29746
29969
  title: "Mark Read",
29747
- description: "Mark message IDs as read for the current agent. Set 'all' to true to mark all unread messages as read.",
29970
+ description: "Mark messages as read. Provide IDs or set 'all' to true.",
29748
29971
  inputSchema: {
29749
29972
  from: exports_external.string().optional().describe("Your agent ID. Falls back to CONVERSATIONS_AGENT_ID env var."),
29750
29973
  ids: exports_external.array(exports_external.number()).optional().describe("Message IDs to mark as read"),
@@ -29759,7 +29982,7 @@ server.registerTool("mark_read", {
29759
29982
  count = markRead(ids, agent);
29760
29983
  } else {
29761
29984
  return {
29762
- content: [{ type: "text", text: "Provide message IDs or set 'all' to true." }],
29985
+ content: [{ type: "text", text: "provide ids or set all=true" }],
29763
29986
  isError: true
29764
29987
  };
29765
29988
  }
@@ -29769,13 +29992,13 @@ server.registerTool("mark_read", {
29769
29992
  });
29770
29993
  server.registerTool("search_messages", {
29771
29994
  title: "Search Messages",
29772
- description: "Full-text search across message content. Returns matching messages ordered by newest first.",
29995
+ description: "Full-text search across message content, newest first.",
29773
29996
  inputSchema: {
29774
- query: exports_external.string().describe("Search query string"),
29775
- space: exports_external.string().optional().describe("Filter by space name"),
29776
- from: exports_external.string().optional().describe("Filter by sender agent ID"),
29777
- to: exports_external.string().optional().describe("Filter by recipient agent ID"),
29778
- limit: exports_external.number().optional().describe("Max results to return (default 50)")
29997
+ query: exports_external.string().describe("Search query"),
29998
+ space: exports_external.string().optional().describe("Filter by space"),
29999
+ from: exports_external.string().optional().describe("Filter by sender"),
30000
+ to: exports_external.string().optional().describe("Filter by recipient"),
30001
+ limit: exports_external.number().optional().describe("Max results (default 20)")
29779
30002
  }
29780
30003
  }, async ({ query, space, from, to, limit }) => {
29781
30004
  const messages = searchMessages({ query, space, from, to, limit });
@@ -29787,11 +30010,11 @@ server.registerTool("export_messages", {
29787
30010
  title: "Export Messages",
29788
30011
  description: "Export messages as JSON or CSV with optional filters.",
29789
30012
  inputSchema: {
29790
- space: exports_external.string().optional().describe("Filter by space name"),
30013
+ space: exports_external.string().optional().describe("Filter by space"),
29791
30014
  session_id: exports_external.string().optional().describe("Filter by session ID"),
29792
- from: exports_external.string().optional().describe("Filter by sender agent ID"),
29793
- since: exports_external.string().optional().describe("Messages after this ISO date"),
29794
- until: exports_external.string().optional().describe("Messages before this ISO date"),
30015
+ from: exports_external.string().optional().describe("Filter by sender"),
30016
+ since: exports_external.string().optional().describe("ISO date lower bound"),
30017
+ until: exports_external.string().optional().describe("ISO date upper bound"),
29795
30018
  format: exports_external.enum(["json", "csv"]).optional().describe("Output format (default: json)")
29796
30019
  }
29797
30020
  }, async ({ space, session_id, from, since, until, format }) => {
@@ -29802,13 +30025,13 @@ server.registerTool("export_messages", {
29802
30025
  });
29803
30026
  server.registerTool("create_space", {
29804
30027
  title: "Create Space",
29805
- description: "Create a new space. The creator is auto-joined. Spaces can be nested (max 3 levels) and associated with a project.",
30028
+ description: "Create a new space. Creator is auto-joined. Supports nesting (max 3 levels) and project association.",
29806
30029
  inputSchema: {
29807
30030
  from: exports_external.string().optional().describe("Your agent ID. Falls back to CONVERSATIONS_AGENT_ID env var."),
29808
- name: exports_external.string().describe("Space name (e.g. 'deployments', 'code-review')"),
30031
+ name: exports_external.string().describe("Space name"),
29809
30032
  description: exports_external.string().optional().describe("Space description"),
29810
- parent_id: exports_external.string().optional().describe("Parent space name for nesting (max 3 levels deep)"),
29811
- project_id: exports_external.string().optional().describe("Project ID to associate this space with")
30033
+ parent_id: exports_external.string().optional().describe("Parent space name (max 3 levels deep)"),
30034
+ project_id: exports_external.string().optional().describe("Project ID to associate with")
29812
30035
  }
29813
30036
  }, async ({ from: fromParam, name, description, parent_id, project_id }) => {
29814
30037
  const agent = resolveIdentity(fromParam);
@@ -29820,7 +30043,7 @@ server.registerTool("create_space", {
29820
30043
  } catch (e) {
29821
30044
  if (e.message?.includes("UNIQUE constraint")) {
29822
30045
  return {
29823
- content: [{ type: "text", text: `Space #${name} already exists` }],
30046
+ content: [{ type: "text", text: `space "${name}" already exists` }],
29824
30047
  isError: true
29825
30048
  };
29826
30049
  }
@@ -29832,11 +30055,11 @@ server.registerTool("create_space", {
29832
30055
  });
29833
30056
  server.registerTool("list_spaces", {
29834
30057
  title: "List Spaces",
29835
- description: "List all available spaces with member and message counts. Can filter by project or parent. Archived spaces are excluded by default.",
30058
+ description: "List spaces with member/message counts. Archived spaces excluded by default.",
29836
30059
  inputSchema: {
29837
30060
  project_id: exports_external.string().optional().describe("Filter by project ID"),
29838
- parent_id: exports_external.string().optional().describe("Filter by parent space name. Use 'null' for top-level only."),
29839
- include_archived: exports_external.boolean().optional().describe("Include archived spaces (default: false)")
30061
+ parent_id: exports_external.string().optional().describe("Filter by parent space. Use 'null' for top-level only."),
30062
+ include_archived: exports_external.boolean().optional().describe("Include archived spaces")
29840
30063
  }
29841
30064
  }, async ({ project_id, parent_id, include_archived }) => {
29842
30065
  const opts = {};
@@ -29862,14 +30085,14 @@ server.registerTool("send_to_space", {
29862
30085
  space: exports_external.string().describe("Space name"),
29863
30086
  content: exports_external.string().describe("Message content"),
29864
30087
  priority: exports_external.enum(["low", "normal", "high", "urgent"]).optional().describe("Message priority"),
29865
- blocking: exports_external.boolean().optional().describe("Send as a blocking message. All space members must acknowledge.")
30088
+ blocking: exports_external.boolean().optional().describe("Blocking message \u2014 all space members must acknowledge")
29866
30089
  }
29867
30090
  }, async ({ from: fromParam, space, content, priority, blocking }) => {
29868
30091
  const from = resolveIdentity(fromParam);
29869
30092
  const sp = getSpace(space);
29870
30093
  if (!sp) {
29871
30094
  return {
29872
- content: [{ type: "text", text: `Space #${space} not found` }],
30095
+ content: [{ type: "text", text: `space "${space}" not found` }],
29873
30096
  isError: true
29874
30097
  };
29875
30098
  }
@@ -29891,7 +30114,7 @@ server.registerTool("read_space", {
29891
30114
  description: "Read messages from a space.",
29892
30115
  inputSchema: {
29893
30116
  space: exports_external.string().describe("Space name"),
29894
- since: exports_external.string().optional().describe("Messages after this ISO timestamp"),
30117
+ since: exports_external.string().optional().describe("ISO timestamp lower bound"),
29895
30118
  limit: exports_external.number().optional().describe("Max messages to return")
29896
30119
  }
29897
30120
  }, async ({ space, since, limit }) => {
@@ -29912,7 +30135,7 @@ server.registerTool("join_space", {
29912
30135
  const ok = joinSpace(space, agent);
29913
30136
  if (!ok) {
29914
30137
  return {
29915
- content: [{ type: "text", text: `Space #${space} not found` }],
30138
+ content: [{ type: "text", text: `space "${space}" not found` }],
29916
30139
  isError: true
29917
30140
  };
29918
30141
  }
@@ -29936,12 +30159,12 @@ server.registerTool("leave_space", {
29936
30159
  });
29937
30160
  server.registerTool("update_space", {
29938
30161
  title: "Update Space",
29939
- description: "Update a space's description, parent, or project association.",
30162
+ description: "Update a space's description, parent, or project.",
29940
30163
  inputSchema: {
29941
- name: exports_external.string().describe("Space name to update"),
30164
+ name: exports_external.string().describe("Space name"),
29942
30165
  description: exports_external.string().optional().describe("New description"),
29943
- parent_id: exports_external.string().optional().describe("New parent space name (use 'null' to remove parent)"),
29944
- project_id: exports_external.string().optional().describe("New project ID (use 'null' to remove project)")
30166
+ parent_id: exports_external.string().optional().describe("New parent space (use 'null' to remove)"),
30167
+ project_id: exports_external.string().optional().describe("New project ID (use 'null' to remove)")
29945
30168
  }
29946
30169
  }, async ({ name, description, parent_id, project_id }) => {
29947
30170
  const updates = {};
@@ -29965,7 +30188,7 @@ server.registerTool("update_space", {
29965
30188
  });
29966
30189
  server.registerTool("archive_space", {
29967
30190
  title: "Archive Space",
29968
- description: "Archive a space. Archived spaces are hidden from list by default.",
30191
+ description: "Archive a space. Hidden from list by default.",
29969
30192
  inputSchema: {
29970
30193
  name: exports_external.string().describe("Space name to archive")
29971
30194
  }
@@ -30003,16 +30226,16 @@ server.registerTool("unarchive_space", {
30003
30226
  });
30004
30227
  server.registerTool("create_project", {
30005
30228
  title: "Create Project",
30006
- description: "Create a new project. Projects organize spaces and provide context for agent collaboration.",
30229
+ description: "Create a new project to organize spaces and agent collaboration.",
30007
30230
  inputSchema: {
30008
30231
  from: exports_external.string().optional().describe("Your agent ID. Falls back to CONVERSATIONS_AGENT_ID env var."),
30009
30232
  name: exports_external.string().describe("Project name (unique)"),
30010
30233
  description: exports_external.string().optional().describe("Project description"),
30011
- path: exports_external.string().optional().describe("Absolute path to project on disk"),
30234
+ path: exports_external.string().optional().describe("Absolute path on disk"),
30012
30235
  repository: exports_external.string().optional().describe("Repository URL"),
30013
- tags: exports_external.string().optional().describe(`JSON array of tags (e.g. '["backend", "api"]')`),
30014
- metadata: exports_external.string().optional().describe("JSON metadata string"),
30015
- settings: exports_external.string().optional().describe("JSON settings string")
30236
+ tags: exports_external.string().optional().describe("JSON array of tags"),
30237
+ metadata: exports_external.string().optional().describe("JSON metadata"),
30238
+ settings: exports_external.string().optional().describe("JSON settings")
30016
30239
  }
30017
30240
  }, async ({ from: fromParam, name, description, path, repository, tags, metadata, settings }) => {
30018
30241
  const agent = resolveIdentity(fromParam);
@@ -30022,7 +30245,7 @@ server.registerTool("create_project", {
30022
30245
  parsedTags = JSON.parse(tags);
30023
30246
  } catch {
30024
30247
  return {
30025
- content: [{ type: "text", text: "Invalid tags JSON. Expected array of strings." }],
30248
+ content: [{ type: "text", text: "invalid tags JSON (expected array)" }],
30026
30249
  isError: true
30027
30250
  };
30028
30251
  }
@@ -30033,7 +30256,7 @@ server.registerTool("create_project", {
30033
30256
  parsedMetadata = JSON.parse(metadata);
30034
30257
  } catch {
30035
30258
  return {
30036
- content: [{ type: "text", text: "Invalid metadata JSON." }],
30259
+ content: [{ type: "text", text: "invalid JSON" }],
30037
30260
  isError: true
30038
30261
  };
30039
30262
  }
@@ -30044,7 +30267,7 @@ server.registerTool("create_project", {
30044
30267
  parsedSettings = JSON.parse(settings);
30045
30268
  } catch {
30046
30269
  return {
30047
- content: [{ type: "text", text: "Invalid settings JSON." }],
30270
+ content: [{ type: "text", text: "invalid JSON" }],
30048
30271
  isError: true
30049
30272
  };
30050
30273
  }
@@ -30066,7 +30289,7 @@ server.registerTool("create_project", {
30066
30289
  } catch (e) {
30067
30290
  if (e.message?.includes("UNIQUE constraint")) {
30068
30291
  return {
30069
- content: [{ type: "text", text: `Project "${name}" already exists` }],
30292
+ content: [{ type: "text", text: `project "${name}" already exists` }],
30070
30293
  isError: true
30071
30294
  };
30072
30295
  }
@@ -30080,7 +30303,7 @@ server.registerTool("list_projects", {
30080
30303
  title: "List Projects",
30081
30304
  description: "List all registered projects.",
30082
30305
  inputSchema: {
30083
- status: exports_external.enum(["active", "archived"]).optional().describe("Filter by project status")
30306
+ status: exports_external.enum(["active", "archived"]).optional().describe("Filter by status")
30084
30307
  }
30085
30308
  }, async ({ status }) => {
30086
30309
  const projects = listProjects(status ? { status } : undefined);
@@ -30101,7 +30324,7 @@ server.registerTool("get_project", {
30101
30324
  }
30102
30325
  if (!project) {
30103
30326
  return {
30104
- content: [{ type: "text", text: `Project "${id}" not found` }],
30327
+ content: [{ type: "text", text: `project "${id}" not found` }],
30105
30328
  isError: true
30106
30329
  };
30107
30330
  }
@@ -30114,14 +30337,14 @@ server.registerTool("update_project", {
30114
30337
  description: "Update a project's fields.",
30115
30338
  inputSchema: {
30116
30339
  id: exports_external.string().describe("Project ID (UUID)"),
30117
- name: exports_external.string().optional().describe("New project name"),
30340
+ name: exports_external.string().optional().describe("New name"),
30118
30341
  description: exports_external.string().optional().describe("New description"),
30119
30342
  path: exports_external.string().optional().describe("New path"),
30120
30343
  status: exports_external.enum(["active", "archived"]).optional().describe("New status"),
30121
30344
  repository: exports_external.string().optional().describe("New repository URL"),
30122
30345
  tags: exports_external.string().optional().describe("JSON array of tags"),
30123
- metadata: exports_external.string().optional().describe("JSON metadata string"),
30124
- settings: exports_external.string().optional().describe("JSON settings string")
30346
+ metadata: exports_external.string().optional().describe("JSON metadata"),
30347
+ settings: exports_external.string().optional().describe("JSON settings")
30125
30348
  }
30126
30349
  }, async ({ id, name, description, path, status, repository, tags, metadata, settings }) => {
30127
30350
  const updates = {};
@@ -30140,7 +30363,7 @@ server.registerTool("update_project", {
30140
30363
  updates.tags = JSON.parse(tags);
30141
30364
  } catch {
30142
30365
  return {
30143
- content: [{ type: "text", text: "Invalid tags JSON." }],
30366
+ content: [{ type: "text", text: "invalid tags JSON" }],
30144
30367
  isError: true
30145
30368
  };
30146
30369
  }
@@ -30150,7 +30373,7 @@ server.registerTool("update_project", {
30150
30373
  updates.metadata = JSON.parse(metadata);
30151
30374
  } catch {
30152
30375
  return {
30153
- content: [{ type: "text", text: "Invalid metadata JSON." }],
30376
+ content: [{ type: "text", text: "invalid JSON" }],
30154
30377
  isError: true
30155
30378
  };
30156
30379
  }
@@ -30160,7 +30383,7 @@ server.registerTool("update_project", {
30160
30383
  updates.settings = JSON.parse(settings);
30161
30384
  } catch {
30162
30385
  return {
30163
- content: [{ type: "text", text: "Invalid settings JSON." }],
30386
+ content: [{ type: "text", text: "invalid JSON" }],
30164
30387
  isError: true
30165
30388
  };
30166
30389
  }
@@ -30179,7 +30402,7 @@ server.registerTool("update_project", {
30179
30402
  });
30180
30403
  server.registerTool("delete_project", {
30181
30404
  title: "Delete Project",
30182
- description: "Delete a project permanently. Fails if spaces still reference it.",
30405
+ description: "Delete a project permanently. Fails if spaces reference it.",
30183
30406
  inputSchema: {
30184
30407
  id: exports_external.string().describe("Project ID (UUID)")
30185
30408
  }
@@ -30188,7 +30411,7 @@ server.registerTool("delete_project", {
30188
30411
  const deleted = deleteProject(id);
30189
30412
  if (!deleted) {
30190
30413
  return {
30191
- content: [{ type: "text", text: `Project "${id}" not found` }],
30414
+ content: [{ type: "text", text: `project "${id}" not found` }],
30192
30415
  isError: true
30193
30416
  };
30194
30417
  }
@@ -30214,7 +30437,7 @@ server.registerTool("delete_message", {
30214
30437
  const deleted = deleteMessage(id, agent);
30215
30438
  if (!deleted) {
30216
30439
  return {
30217
- content: [{ type: "text", text: `Message #${id} not found or not your message` }],
30440
+ content: [{ type: "text", text: `not found or forbidden` }],
30218
30441
  isError: true
30219
30442
  };
30220
30443
  }
@@ -30235,7 +30458,7 @@ server.registerTool("edit_message", {
30235
30458
  const msg = editMessage(id, agent, content);
30236
30459
  if (!msg) {
30237
30460
  return {
30238
- content: [{ type: "text", text: `Message #${id} not found or not your message` }],
30461
+ content: [{ type: "text", text: `not found or forbidden` }],
30239
30462
  isError: true
30240
30463
  };
30241
30464
  }
@@ -30245,7 +30468,7 @@ server.registerTool("edit_message", {
30245
30468
  });
30246
30469
  server.registerTool("pin_message", {
30247
30470
  title: "Pin Message",
30248
- description: "Pin a message. Pinned messages can be retrieved with get_pinned_messages.",
30471
+ description: "Pin a message. Retrieve pinned messages with get_pinned_messages.",
30249
30472
  inputSchema: {
30250
30473
  id: exports_external.number().describe("Message ID to pin")
30251
30474
  }
@@ -30253,7 +30476,7 @@ server.registerTool("pin_message", {
30253
30476
  const msg = pinMessage(id);
30254
30477
  if (!msg) {
30255
30478
  return {
30256
- content: [{ type: "text", text: `Message #${id} not found` }],
30479
+ content: [{ type: "text", text: `message #${id} not found` }],
30257
30480
  isError: true
30258
30481
  };
30259
30482
  }
@@ -30271,7 +30494,7 @@ server.registerTool("unpin_message", {
30271
30494
  const msg = unpinMessage(id);
30272
30495
  if (!msg) {
30273
30496
  return {
30274
- content: [{ type: "text", text: `Message #${id} not found` }],
30497
+ content: [{ type: "text", text: `message #${id} not found` }],
30275
30498
  isError: true
30276
30499
  };
30277
30500
  }
@@ -30283,7 +30506,7 @@ server.registerTool("get_pinned_messages", {
30283
30506
  title: "Get Pinned Messages",
30284
30507
  description: "Retrieve pinned messages, optionally filtered by space or session.",
30285
30508
  inputSchema: {
30286
- space: exports_external.string().optional().describe("Filter by space name"),
30509
+ space: exports_external.string().optional().describe("Filter by space"),
30287
30510
  session_id: exports_external.string().optional().describe("Filter by session ID"),
30288
30511
  limit: exports_external.number().optional().describe("Max messages to return")
30289
30512
  }
@@ -30309,9 +30532,9 @@ server.registerTool("heartbeat", {
30309
30532
  });
30310
30533
  server.registerTool("list_agents", {
30311
30534
  title: "List Agents",
30312
- description: "List all agents with their presence status. Returns agent name, status, last seen time, and whether they are online.",
30535
+ description: "List agents with presence status (name, status, last_seen, online).",
30313
30536
  inputSchema: {
30314
- online_only: exports_external.boolean().optional().describe("Only return agents that are currently online (seen within last 60 seconds)")
30537
+ online_only: exports_external.boolean().optional().describe("Only return agents online within last 60s")
30315
30538
  }
30316
30539
  }, async ({ online_only }) => {
30317
30540
  const agents = listAgents({ online_only });
@@ -30321,7 +30544,7 @@ server.registerTool("list_agents", {
30321
30544
  });
30322
30545
  server.registerTool("get_blockers", {
30323
30546
  title: "Get Blockers",
30324
- description: "Check for unread blocking messages targeting you. Returns messages that must be acknowledged before continuing.",
30547
+ description: "Check for unread blocking messages targeting you. Must acknowledge before continuing.",
30325
30548
  inputSchema: {
30326
30549
  from: exports_external.string().optional().describe("Your agent ID. Falls back to CONVERSATIONS_AGENT_ID env var.")
30327
30550
  }
@@ -30334,7 +30557,7 @@ server.registerTool("get_blockers", {
30334
30557
  });
30335
30558
  server.registerTool("remove_agent", {
30336
30559
  title: "Remove Agent",
30337
- description: "Remove an agent from the presence list. Only the agent itself should remove its own presence.",
30560
+ description: "Remove an agent from the presence list.",
30338
30561
  inputSchema: {
30339
30562
  from: exports_external.string().optional().describe("Your agent ID. Falls back to CONVERSATIONS_AGENT_ID env var."),
30340
30563
  agent: exports_external.string().optional().describe("Agent to remove (defaults to yourself)")
@@ -30345,7 +30568,7 @@ server.registerTool("remove_agent", {
30345
30568
  const removed = removePresence(agent);
30346
30569
  if (!removed) {
30347
30570
  return {
30348
- content: [{ type: "text", text: `Agent "${agent}" not found` }],
30571
+ content: [{ type: "text", text: `agent "${agent}" not found` }],
30349
30572
  isError: true
30350
30573
  };
30351
30574
  }
@@ -30355,7 +30578,7 @@ server.registerTool("remove_agent", {
30355
30578
  });
30356
30579
  server.registerTool("rename_agent", {
30357
30580
  title: "Rename Agent",
30358
- description: "Rename an agent in the presence list. By default renames yourself.",
30581
+ description: "Rename an agent in the presence list. Defaults to renaming yourself.",
30359
30582
  inputSchema: {
30360
30583
  from: exports_external.string().optional().describe("Your current agent ID. Falls back to CONVERSATIONS_AGENT_ID env var."),
30361
30584
  new_name: exports_external.string().describe("The new name for the agent")
@@ -30365,7 +30588,7 @@ server.registerTool("rename_agent", {
30365
30588
  const newName = new_name.trim();
30366
30589
  if (!newName) {
30367
30590
  return {
30368
- content: [{ type: "text", text: "New name cannot be empty" }],
30591
+ content: [{ type: "text", text: "new name cannot be empty" }],
30369
30592
  isError: true
30370
30593
  };
30371
30594
  }
@@ -30373,7 +30596,7 @@ server.registerTool("rename_agent", {
30373
30596
  const renamed = renameAgent(oldName, newName);
30374
30597
  if (!renamed) {
30375
30598
  return {
30376
- content: [{ type: "text", text: `Agent "${oldName}" not found in presence list` }],
30599
+ content: [{ type: "text", text: `agent "${oldName}" not found` }],
30377
30600
  isError: true
30378
30601
  };
30379
30602
  }