@hasna/conversations 0.2.15 → 0.2.17
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/hook.js +10 -0
- package/bin/index.js +84 -2
- package/bin/mcp.js +84 -2
- package/dist/index.js +10 -0
- package/dist/lib/messages.d.ts +16 -0
- package/package.json +1 -1
package/bin/hook.js
CHANGED
|
@@ -255,6 +255,16 @@ function getDb() {
|
|
|
255
255
|
if (!presenceColNames.includes("project_id")) {
|
|
256
256
|
db.exec("ALTER TABLE agent_presence ADD COLUMN project_id TEXT");
|
|
257
257
|
}
|
|
258
|
+
db.exec(`
|
|
259
|
+
CREATE TABLE IF NOT EXISTS message_read_receipts (
|
|
260
|
+
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
261
|
+
agent TEXT NOT NULL,
|
|
262
|
+
read_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
263
|
+
PRIMARY KEY (message_id, agent)
|
|
264
|
+
)
|
|
265
|
+
`);
|
|
266
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_read_receipts_message ON message_read_receipts(message_id)");
|
|
267
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_read_receipts_agent ON message_read_receipts(agent)");
|
|
258
268
|
db.exec(`
|
|
259
269
|
CREATE TABLE IF NOT EXISTS message_mentions (
|
|
260
270
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
package/bin/index.js
CHANGED
|
@@ -2120,6 +2120,16 @@ function getDb() {
|
|
|
2120
2120
|
if (!presenceColNames.includes("project_id")) {
|
|
2121
2121
|
db.exec("ALTER TABLE agent_presence ADD COLUMN project_id TEXT");
|
|
2122
2122
|
}
|
|
2123
|
+
db.exec(`
|
|
2124
|
+
CREATE TABLE IF NOT EXISTS message_read_receipts (
|
|
2125
|
+
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
2126
|
+
agent TEXT NOT NULL,
|
|
2127
|
+
read_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
2128
|
+
PRIMARY KEY (message_id, agent)
|
|
2129
|
+
)
|
|
2130
|
+
`);
|
|
2131
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_read_receipts_message ON message_read_receipts(message_id)");
|
|
2132
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_read_receipts_agent ON message_read_receipts(agent)");
|
|
2123
2133
|
db.exec(`
|
|
2124
2134
|
CREATE TABLE IF NOT EXISTS message_mentions (
|
|
2125
2135
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -2814,6 +2824,33 @@ function markUnreadByIds(ids) {
|
|
|
2814
2824
|
const result = db2.prepare(`UPDATE messages SET read_at = NULL WHERE id IN (${placeholders})`).run(...ids);
|
|
2815
2825
|
return result.changes;
|
|
2816
2826
|
}
|
|
2827
|
+
function recordReadReceipt(messageId, agent) {
|
|
2828
|
+
const db2 = getDb();
|
|
2829
|
+
db2.prepare(`INSERT OR REPLACE INTO message_read_receipts (message_id, agent, read_at)
|
|
2830
|
+
VALUES (?, ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'))`).run(messageId, agent.toLowerCase());
|
|
2831
|
+
}
|
|
2832
|
+
function recordReadReceiptsBatch(messageIds, agent) {
|
|
2833
|
+
if (!messageIds.length || !agent)
|
|
2834
|
+
return;
|
|
2835
|
+
const db2 = getDb();
|
|
2836
|
+
const stmt = db2.prepare(`INSERT OR REPLACE INTO message_read_receipts (message_id, agent, read_at)
|
|
2837
|
+
VALUES (?, ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'))`);
|
|
2838
|
+
for (const id of messageIds) {
|
|
2839
|
+
stmt.run(id, agent.toLowerCase());
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
function getReadReceipts(messageId) {
|
|
2843
|
+
const db2 = getDb();
|
|
2844
|
+
return db2.prepare("SELECT * FROM message_read_receipts WHERE message_id = ? ORDER BY read_at ASC").all(messageId);
|
|
2845
|
+
}
|
|
2846
|
+
function getMessageReadStatus(messageId, space) {
|
|
2847
|
+
const db2 = getDb();
|
|
2848
|
+
const receipts = getReadReceipts(messageId);
|
|
2849
|
+
const readers = new Set(receipts.map((r) => r.agent));
|
|
2850
|
+
const members = db2.prepare("SELECT agent FROM space_members WHERE space = ?").all(space);
|
|
2851
|
+
const unread_by = members.map((m) => m.agent).filter((a) => !readers.has(a));
|
|
2852
|
+
return { receipts, unread_by };
|
|
2853
|
+
}
|
|
2817
2854
|
var init_messages = __esm(() => {
|
|
2818
2855
|
init_db();
|
|
2819
2856
|
init_webhooks();
|
|
@@ -4453,7 +4490,7 @@ var init_poll = __esm(() => {
|
|
|
4453
4490
|
var require_package = __commonJS((exports, module) => {
|
|
4454
4491
|
module.exports = {
|
|
4455
4492
|
name: "@hasna/conversations",
|
|
4456
|
-
version: "0.2.
|
|
4493
|
+
version: "0.2.17",
|
|
4457
4494
|
description: "Real-time CLI messaging for AI agents",
|
|
4458
4495
|
type: "module",
|
|
4459
4496
|
bin: {
|
|
@@ -33914,6 +33951,46 @@ var init_mcp2 = __esm(() => {
|
|
|
33914
33951
|
content: [{ type: "text", text: JSON.stringify(spaces) }]
|
|
33915
33952
|
};
|
|
33916
33953
|
});
|
|
33954
|
+
server.registerTool("read_receipts", {
|
|
33955
|
+
description: "Get per-agent read receipts for a message. Shows who has read it and (for space messages) who hasn't.",
|
|
33956
|
+
inputSchema: {
|
|
33957
|
+
message_id: exports_external.coerce.number(),
|
|
33958
|
+
space: exports_external.string().optional().describe("Space name \u2014 if provided, also returns list of members who haven't read yet")
|
|
33959
|
+
}
|
|
33960
|
+
}, async (args) => {
|
|
33961
|
+
const receipts = getReadReceipts(args.message_id);
|
|
33962
|
+
if (args.space) {
|
|
33963
|
+
const status = getMessageReadStatus(args.message_id, args.space);
|
|
33964
|
+
return { content: [{ type: "text", text: JSON.stringify(status) }] };
|
|
33965
|
+
}
|
|
33966
|
+
return { content: [{ type: "text", text: JSON.stringify({ receipts, count: receipts.length }) }] };
|
|
33967
|
+
});
|
|
33968
|
+
server.registerTool("mark_read_receipt", {
|
|
33969
|
+
description: "Manually record that an agent has read a specific message.",
|
|
33970
|
+
inputSchema: {
|
|
33971
|
+
message_id: exports_external.coerce.number(),
|
|
33972
|
+
agent: exports_external.string()
|
|
33973
|
+
}
|
|
33974
|
+
}, async (args) => {
|
|
33975
|
+
recordReadReceipt(args.message_id, args.agent);
|
|
33976
|
+
return { content: [{ type: "text", text: `\u2713 Marked message #${args.message_id} as read by ${args.agent}` }] };
|
|
33977
|
+
});
|
|
33978
|
+
server.registerTool("react", {
|
|
33979
|
+
description: "Add an emoji reaction (alias for add_reaction). Quick acknowledgment without a full reply.",
|
|
33980
|
+
inputSchema: { message_id: exports_external.coerce.number(), emoji: exports_external.string(), from: exports_external.string().optional() }
|
|
33981
|
+
}, async (args) => {
|
|
33982
|
+
const agent = resolveIdentity(args.from);
|
|
33983
|
+
const reaction = addReaction(args.message_id, agent, args.emoji);
|
|
33984
|
+
return { content: [{ type: "text", text: JSON.stringify(reaction) }] };
|
|
33985
|
+
});
|
|
33986
|
+
server.registerTool("unreact", {
|
|
33987
|
+
description: "Remove an emoji reaction (alias for remove_reaction).",
|
|
33988
|
+
inputSchema: { message_id: exports_external.coerce.number(), emoji: exports_external.string(), from: exports_external.string().optional() }
|
|
33989
|
+
}, async (args) => {
|
|
33990
|
+
const agent = resolveIdentity(args.from);
|
|
33991
|
+
const removed = removeReaction(args.message_id, agent, args.emoji);
|
|
33992
|
+
return { content: [{ type: "text", text: JSON.stringify({ removed }) }] };
|
|
33993
|
+
});
|
|
33917
33994
|
server.registerTool("broadcast", {
|
|
33918
33995
|
description: "Send the same message to multiple spaces at once. Useful for status updates, bug reports, or announcements that need to go to several spaces.",
|
|
33919
33996
|
inputSchema: {
|
|
@@ -34015,6 +34092,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34015
34092
|
description: "Read messages from a space.",
|
|
34016
34093
|
inputSchema: {
|
|
34017
34094
|
space: exports_external.string(),
|
|
34095
|
+
from: exports_external.string().optional().describe("Agent reading the space \u2014 used for per-agent read receipts"),
|
|
34018
34096
|
since: exports_external.string().optional(),
|
|
34019
34097
|
limit: exports_external.coerce.number().optional(),
|
|
34020
34098
|
mark_read: exports_external.coerce.boolean().optional(),
|
|
@@ -34024,11 +34102,15 @@ var init_mcp2 = __esm(() => {
|
|
|
34024
34102
|
latest: exports_external.coerce.number().optional().describe("Return the N most recent messages, newest first")
|
|
34025
34103
|
}
|
|
34026
34104
|
}, async (args) => {
|
|
34027
|
-
const { space, since, limit, mark_read, max_content_length, threads_only, include_reply_counts, latest } = args;
|
|
34105
|
+
const { space, from: fromParam, since, limit, mark_read, max_content_length, threads_only, include_reply_counts, latest } = args;
|
|
34028
34106
|
const messages = readMessages({ space, since, limit, max_content_length, threads_only, include_reply_counts, latest });
|
|
34029
34107
|
if (mark_read !== false && messages.length > 0) {
|
|
34030
34108
|
markReadByIds(messages.map((m) => m.id));
|
|
34031
34109
|
}
|
|
34110
|
+
if (fromParam && messages.length > 0) {
|
|
34111
|
+
const agent = resolveIdentity(fromParam);
|
|
34112
|
+
recordReadReceiptsBatch(messages.map((m) => m.id), agent);
|
|
34113
|
+
}
|
|
34032
34114
|
return {
|
|
34033
34115
|
content: [{ type: "text", text: JSON.stringify(messages) }]
|
|
34034
34116
|
};
|
package/bin/mcp.js
CHANGED
|
@@ -6752,6 +6752,16 @@ function getDb() {
|
|
|
6752
6752
|
if (!presenceColNames.includes("project_id")) {
|
|
6753
6753
|
db.exec("ALTER TABLE agent_presence ADD COLUMN project_id TEXT");
|
|
6754
6754
|
}
|
|
6755
|
+
db.exec(`
|
|
6756
|
+
CREATE TABLE IF NOT EXISTS message_read_receipts (
|
|
6757
|
+
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
6758
|
+
agent TEXT NOT NULL,
|
|
6759
|
+
read_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
6760
|
+
PRIMARY KEY (message_id, agent)
|
|
6761
|
+
)
|
|
6762
|
+
`);
|
|
6763
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_read_receipts_message ON message_read_receipts(message_id)");
|
|
6764
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_read_receipts_agent ON message_read_receipts(agent)");
|
|
6755
6765
|
db.exec(`
|
|
6756
6766
|
CREATE TABLE IF NOT EXISTS message_mentions (
|
|
6757
6767
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -29317,6 +29327,33 @@ function markUnreadByIds(ids) {
|
|
|
29317
29327
|
const result = db2.prepare(`UPDATE messages SET read_at = NULL WHERE id IN (${placeholders})`).run(...ids);
|
|
29318
29328
|
return result.changes;
|
|
29319
29329
|
}
|
|
29330
|
+
function recordReadReceipt(messageId, agent) {
|
|
29331
|
+
const db2 = getDb();
|
|
29332
|
+
db2.prepare(`INSERT OR REPLACE INTO message_read_receipts (message_id, agent, read_at)
|
|
29333
|
+
VALUES (?, ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'))`).run(messageId, agent.toLowerCase());
|
|
29334
|
+
}
|
|
29335
|
+
function recordReadReceiptsBatch(messageIds, agent) {
|
|
29336
|
+
if (!messageIds.length || !agent)
|
|
29337
|
+
return;
|
|
29338
|
+
const db2 = getDb();
|
|
29339
|
+
const stmt = db2.prepare(`INSERT OR REPLACE INTO message_read_receipts (message_id, agent, read_at)
|
|
29340
|
+
VALUES (?, ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'))`);
|
|
29341
|
+
for (const id of messageIds) {
|
|
29342
|
+
stmt.run(id, agent.toLowerCase());
|
|
29343
|
+
}
|
|
29344
|
+
}
|
|
29345
|
+
function getReadReceipts(messageId) {
|
|
29346
|
+
const db2 = getDb();
|
|
29347
|
+
return db2.prepare("SELECT * FROM message_read_receipts WHERE message_id = ? ORDER BY read_at ASC").all(messageId);
|
|
29348
|
+
}
|
|
29349
|
+
function getMessageReadStatus(messageId, space) {
|
|
29350
|
+
const db2 = getDb();
|
|
29351
|
+
const receipts = getReadReceipts(messageId);
|
|
29352
|
+
const readers = new Set(receipts.map((r) => r.agent));
|
|
29353
|
+
const members = db2.prepare("SELECT agent FROM space_members WHERE space = ?").all(space);
|
|
29354
|
+
const unread_by = members.map((m) => m.agent).filter((a) => !readers.has(a));
|
|
29355
|
+
return { receipts, unread_by };
|
|
29356
|
+
}
|
|
29320
29357
|
|
|
29321
29358
|
// src/lib/sessions.ts
|
|
29322
29359
|
init_db();
|
|
@@ -30943,7 +30980,7 @@ function getGraphStats() {
|
|
|
30943
30980
|
// package.json
|
|
30944
30981
|
var package_default = {
|
|
30945
30982
|
name: "@hasna/conversations",
|
|
30946
|
-
version: "0.2.
|
|
30983
|
+
version: "0.2.17",
|
|
30947
30984
|
description: "Real-time CLI messaging for AI agents",
|
|
30948
30985
|
type: "module",
|
|
30949
30986
|
bin: {
|
|
@@ -31283,6 +31320,46 @@ server.registerTool("list_spaces", {
|
|
|
31283
31320
|
content: [{ type: "text", text: JSON.stringify(spaces) }]
|
|
31284
31321
|
};
|
|
31285
31322
|
});
|
|
31323
|
+
server.registerTool("read_receipts", {
|
|
31324
|
+
description: "Get per-agent read receipts for a message. Shows who has read it and (for space messages) who hasn't.",
|
|
31325
|
+
inputSchema: {
|
|
31326
|
+
message_id: exports_external.coerce.number(),
|
|
31327
|
+
space: exports_external.string().optional().describe("Space name \u2014 if provided, also returns list of members who haven't read yet")
|
|
31328
|
+
}
|
|
31329
|
+
}, async (args) => {
|
|
31330
|
+
const receipts = getReadReceipts(args.message_id);
|
|
31331
|
+
if (args.space) {
|
|
31332
|
+
const status = getMessageReadStatus(args.message_id, args.space);
|
|
31333
|
+
return { content: [{ type: "text", text: JSON.stringify(status) }] };
|
|
31334
|
+
}
|
|
31335
|
+
return { content: [{ type: "text", text: JSON.stringify({ receipts, count: receipts.length }) }] };
|
|
31336
|
+
});
|
|
31337
|
+
server.registerTool("mark_read_receipt", {
|
|
31338
|
+
description: "Manually record that an agent has read a specific message.",
|
|
31339
|
+
inputSchema: {
|
|
31340
|
+
message_id: exports_external.coerce.number(),
|
|
31341
|
+
agent: exports_external.string()
|
|
31342
|
+
}
|
|
31343
|
+
}, async (args) => {
|
|
31344
|
+
recordReadReceipt(args.message_id, args.agent);
|
|
31345
|
+
return { content: [{ type: "text", text: `\u2713 Marked message #${args.message_id} as read by ${args.agent}` }] };
|
|
31346
|
+
});
|
|
31347
|
+
server.registerTool("react", {
|
|
31348
|
+
description: "Add an emoji reaction (alias for add_reaction). Quick acknowledgment without a full reply.",
|
|
31349
|
+
inputSchema: { message_id: exports_external.coerce.number(), emoji: exports_external.string(), from: exports_external.string().optional() }
|
|
31350
|
+
}, async (args) => {
|
|
31351
|
+
const agent = resolveIdentity(args.from);
|
|
31352
|
+
const reaction = addReaction(args.message_id, agent, args.emoji);
|
|
31353
|
+
return { content: [{ type: "text", text: JSON.stringify(reaction) }] };
|
|
31354
|
+
});
|
|
31355
|
+
server.registerTool("unreact", {
|
|
31356
|
+
description: "Remove an emoji reaction (alias for remove_reaction).",
|
|
31357
|
+
inputSchema: { message_id: exports_external.coerce.number(), emoji: exports_external.string(), from: exports_external.string().optional() }
|
|
31358
|
+
}, async (args) => {
|
|
31359
|
+
const agent = resolveIdentity(args.from);
|
|
31360
|
+
const removed = removeReaction(args.message_id, agent, args.emoji);
|
|
31361
|
+
return { content: [{ type: "text", text: JSON.stringify({ removed }) }] };
|
|
31362
|
+
});
|
|
31286
31363
|
server.registerTool("broadcast", {
|
|
31287
31364
|
description: "Send the same message to multiple spaces at once. Useful for status updates, bug reports, or announcements that need to go to several spaces.",
|
|
31288
31365
|
inputSchema: {
|
|
@@ -31384,6 +31461,7 @@ server.registerTool("read_space", {
|
|
|
31384
31461
|
description: "Read messages from a space.",
|
|
31385
31462
|
inputSchema: {
|
|
31386
31463
|
space: exports_external.string(),
|
|
31464
|
+
from: exports_external.string().optional().describe("Agent reading the space \u2014 used for per-agent read receipts"),
|
|
31387
31465
|
since: exports_external.string().optional(),
|
|
31388
31466
|
limit: exports_external.coerce.number().optional(),
|
|
31389
31467
|
mark_read: exports_external.coerce.boolean().optional(),
|
|
@@ -31393,11 +31471,15 @@ server.registerTool("read_space", {
|
|
|
31393
31471
|
latest: exports_external.coerce.number().optional().describe("Return the N most recent messages, newest first")
|
|
31394
31472
|
}
|
|
31395
31473
|
}, async (args) => {
|
|
31396
|
-
const { space, since, limit, mark_read, max_content_length, threads_only, include_reply_counts, latest } = args;
|
|
31474
|
+
const { space, from: fromParam, since, limit, mark_read, max_content_length, threads_only, include_reply_counts, latest } = args;
|
|
31397
31475
|
const messages = readMessages({ space, since, limit, max_content_length, threads_only, include_reply_counts, latest });
|
|
31398
31476
|
if (mark_read !== false && messages.length > 0) {
|
|
31399
31477
|
markReadByIds(messages.map((m) => m.id));
|
|
31400
31478
|
}
|
|
31479
|
+
if (fromParam && messages.length > 0) {
|
|
31480
|
+
const agent = resolveIdentity(fromParam);
|
|
31481
|
+
recordReadReceiptsBatch(messages.map((m) => m.id), agent);
|
|
31482
|
+
}
|
|
31401
31483
|
return {
|
|
31402
31484
|
content: [{ type: "text", text: JSON.stringify(messages) }]
|
|
31403
31485
|
};
|
package/dist/index.js
CHANGED
|
@@ -279,6 +279,16 @@ function getDb() {
|
|
|
279
279
|
if (!presenceColNames.includes("project_id")) {
|
|
280
280
|
db.exec("ALTER TABLE agent_presence ADD COLUMN project_id TEXT");
|
|
281
281
|
}
|
|
282
|
+
db.exec(`
|
|
283
|
+
CREATE TABLE IF NOT EXISTS message_read_receipts (
|
|
284
|
+
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
285
|
+
agent TEXT NOT NULL,
|
|
286
|
+
read_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
287
|
+
PRIMARY KEY (message_id, agent)
|
|
288
|
+
)
|
|
289
|
+
`);
|
|
290
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_read_receipts_message ON message_read_receipts(message_id)");
|
|
291
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_read_receipts_agent ON message_read_receipts(agent)");
|
|
282
292
|
db.exec(`
|
|
283
293
|
CREATE TABLE IF NOT EXISTS message_mentions (
|
|
284
294
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
package/dist/lib/messages.d.ts
CHANGED
|
@@ -92,3 +92,19 @@ export declare function markMentionsRead(agent: string, space?: string): number;
|
|
|
92
92
|
export declare function markUnread(messageId: number): number;
|
|
93
93
|
/** Mark multiple messages as unread. */
|
|
94
94
|
export declare function markUnreadByIds(ids: number[]): number;
|
|
95
|
+
export interface ReadReceipt {
|
|
96
|
+
message_id: number;
|
|
97
|
+
agent: string;
|
|
98
|
+
read_at: string;
|
|
99
|
+
}
|
|
100
|
+
/** Record that an agent has read a specific message. */
|
|
101
|
+
export declare function recordReadReceipt(messageId: number, agent: string): void;
|
|
102
|
+
/** Record read receipts for all messages in a batch. */
|
|
103
|
+
export declare function recordReadReceiptsBatch(messageIds: number[], agent: string): void;
|
|
104
|
+
/** Get all read receipts for a specific message. */
|
|
105
|
+
export declare function getReadReceipts(messageId: number): ReadReceipt[];
|
|
106
|
+
/** Get read status summary for a space message: who has read it and who hasn't. */
|
|
107
|
+
export declare function getMessageReadStatus(messageId: number, space: string): {
|
|
108
|
+
receipts: ReadReceipt[];
|
|
109
|
+
unread_by: string[];
|
|
110
|
+
};
|