@hasna/conversations 0.2.44 → 0.2.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/hook.js +35 -0
- package/bin/index.js +635 -84
- package/bin/mcp.js +393 -63
- package/dist/index.d.ts +1 -0
- package/dist/index.js +159 -0
- package/dist/lib/space-notifications.d.ts +19 -0
- package/dist/lib/space-notifications.test.d.ts +1 -0
- package/dist/mcp/channel.d.ts +4 -1
- package/dist/mcp/channel.test.d.ts +1 -0
- package/dist/mcp/tools/messaging.d.ts +1 -1
- package/dist/mcp/tools/spaces.d.ts +2 -0
- package/dist/types.d.ts +17 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -13571,6 +13571,18 @@ function getDb() {
|
|
|
13571
13571
|
PRIMARY KEY (space, agent)
|
|
13572
13572
|
)
|
|
13573
13573
|
`);
|
|
13574
|
+
db.exec(`
|
|
13575
|
+
CREATE TABLE IF NOT EXISTS space_subscriptions (
|
|
13576
|
+
space TEXT NOT NULL REFERENCES spaces(name),
|
|
13577
|
+
agent TEXT NOT NULL,
|
|
13578
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
13579
|
+
preview_chars INTEGER NOT NULL DEFAULT 140,
|
|
13580
|
+
since_message_id INTEGER NOT NULL DEFAULT 0,
|
|
13581
|
+
PRIMARY KEY (space, agent)
|
|
13582
|
+
)
|
|
13583
|
+
`);
|
|
13584
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_space_subscriptions_agent ON space_subscriptions(agent)");
|
|
13585
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_space_subscriptions_space ON space_subscriptions(space)");
|
|
13574
13586
|
db.exec(`
|
|
13575
13587
|
CREATE TABLE IF NOT EXISTS agent_presence (
|
|
13576
13588
|
id TEXT NOT NULL,
|
|
@@ -13611,6 +13623,16 @@ function getDb() {
|
|
|
13611
13623
|
)
|
|
13612
13624
|
`);
|
|
13613
13625
|
db.exec("CREATE INDEX IF NOT EXISTS idx_reactions_message ON reactions(message_id)");
|
|
13626
|
+
db.exec(`
|
|
13627
|
+
CREATE TABLE IF NOT EXISTS space_notification_reads (
|
|
13628
|
+
agent TEXT NOT NULL,
|
|
13629
|
+
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
13630
|
+
read_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
13631
|
+
PRIMARY KEY (agent, message_id)
|
|
13632
|
+
)
|
|
13633
|
+
`);
|
|
13634
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_space_notification_reads_agent ON space_notification_reads(agent)");
|
|
13635
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_space_notification_reads_message ON space_notification_reads(message_id)");
|
|
13614
13636
|
const existingTables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();
|
|
13615
13637
|
const tableNames = existingTables.map((t) => t.name);
|
|
13616
13638
|
if (tableNames.includes("channels") && tableNames.includes("spaces")) {
|
|
@@ -13658,6 +13680,19 @@ function getDb() {
|
|
|
13658
13680
|
if (!spaceColNames.includes("topic")) {
|
|
13659
13681
|
db.exec("ALTER TABLE spaces ADD COLUMN topic TEXT");
|
|
13660
13682
|
}
|
|
13683
|
+
const spaceSubscriptionCols = db.prepare("PRAGMA table_info(space_subscriptions)").all();
|
|
13684
|
+
const spaceSubscriptionColNames = spaceSubscriptionCols.map((c) => c.name);
|
|
13685
|
+
if (!spaceSubscriptionColNames.includes("since_message_id")) {
|
|
13686
|
+
db.exec("ALTER TABLE space_subscriptions ADD COLUMN since_message_id INTEGER NOT NULL DEFAULT 0");
|
|
13687
|
+
db.exec(`
|
|
13688
|
+
UPDATE space_subscriptions
|
|
13689
|
+
SET since_message_id = COALESCE(
|
|
13690
|
+
(SELECT MAX(m.id) FROM messages m WHERE m.space = space_subscriptions.space),
|
|
13691
|
+
0
|
|
13692
|
+
)
|
|
13693
|
+
WHERE since_message_id = 0
|
|
13694
|
+
`);
|
|
13695
|
+
}
|
|
13661
13696
|
const msgCols2 = db.prepare("PRAGMA table_info(messages)").all();
|
|
13662
13697
|
const colNames2 = msgCols2.map((c) => c.name);
|
|
13663
13698
|
if (!colNames2.includes("edited_at")) {
|
|
@@ -15261,11 +15296,127 @@ var init_presence = __esm(() => {
|
|
|
15261
15296
|
CONFLICT_THRESHOLD_SECONDS = 30 * 60;
|
|
15262
15297
|
});
|
|
15263
15298
|
|
|
15299
|
+
// src/lib/space-notifications.ts
|
|
15300
|
+
function buildMessagePreview(content, maxChars = DEFAULT_PREVIEW_CHARS) {
|
|
15301
|
+
const normalized = content.replace(/[*#`~_>\-]/g, " ").replace(/\s+/g, " ").trim();
|
|
15302
|
+
if (normalized.length <= maxChars)
|
|
15303
|
+
return normalized;
|
|
15304
|
+
return normalized.slice(0, Math.max(1, maxChars)).trimEnd() + "\u2026";
|
|
15305
|
+
}
|
|
15306
|
+
function subscribeToSpaceNotifications(space, agent, opts) {
|
|
15307
|
+
const db2 = getDb();
|
|
15308
|
+
const existingSpace = db2.prepare("SELECT name FROM spaces WHERE name = ?").get(space);
|
|
15309
|
+
if (!existingSpace) {
|
|
15310
|
+
throw new Error(`Space not found: ${space}`);
|
|
15311
|
+
}
|
|
15312
|
+
const previewChars = Number.isFinite(opts?.preview_chars) && opts?.preview_chars > 0 ? Math.floor(opts.preview_chars) : DEFAULT_PREVIEW_CHARS;
|
|
15313
|
+
const currentMaxMessageId = db2.prepare("SELECT COALESCE(MAX(id), 0) AS max_id FROM messages WHERE space = ?").get(space).max_id;
|
|
15314
|
+
db2.prepare(`
|
|
15315
|
+
INSERT INTO space_subscriptions (space, agent, preview_chars, since_message_id)
|
|
15316
|
+
VALUES (?, ?, ?, ?)
|
|
15317
|
+
ON CONFLICT(space, agent) DO UPDATE SET preview_chars = excluded.preview_chars
|
|
15318
|
+
`).run(space, agent, previewChars, currentMaxMessageId);
|
|
15319
|
+
return db2.prepare("SELECT space, agent, created_at, preview_chars, since_message_id FROM space_subscriptions WHERE space = ? AND agent = ?").get(space, agent);
|
|
15320
|
+
}
|
|
15321
|
+
function unsubscribeFromSpaceNotifications(space, agent) {
|
|
15322
|
+
const db2 = getDb();
|
|
15323
|
+
const result = db2.prepare("DELETE FROM space_subscriptions WHERE space = ? AND agent = ?").run(space, agent);
|
|
15324
|
+
return result.changes > 0;
|
|
15325
|
+
}
|
|
15326
|
+
function listSpaceNotificationSubscriptions(agent) {
|
|
15327
|
+
const db2 = getDb();
|
|
15328
|
+
if (agent) {
|
|
15329
|
+
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);
|
|
15330
|
+
}
|
|
15331
|
+
return db2.prepare("SELECT space, agent, created_at, preview_chars, since_message_id FROM space_subscriptions ORDER BY agent ASC, space ASC").all();
|
|
15332
|
+
}
|
|
15333
|
+
function readSpaceNotifications(opts) {
|
|
15334
|
+
const db2 = getDb();
|
|
15335
|
+
const conditions = [
|
|
15336
|
+
"s.agent = ?",
|
|
15337
|
+
"m.space IS NOT NULL",
|
|
15338
|
+
"m.from_agent != ?",
|
|
15339
|
+
"m.id > s.since_message_id"
|
|
15340
|
+
];
|
|
15341
|
+
const params = [opts.agent, opts.agent];
|
|
15342
|
+
if (opts.space) {
|
|
15343
|
+
conditions.push("m.space = ?");
|
|
15344
|
+
params.push(opts.space);
|
|
15345
|
+
}
|
|
15346
|
+
if (opts.since) {
|
|
15347
|
+
conditions.push("m.created_at > ?");
|
|
15348
|
+
params.push(opts.since);
|
|
15349
|
+
}
|
|
15350
|
+
if (opts.unread_only !== false) {
|
|
15351
|
+
conditions.push("snr.message_id IS NULL");
|
|
15352
|
+
}
|
|
15353
|
+
const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
|
|
15354
|
+
const rows = db2.prepare(`
|
|
15355
|
+
SELECT
|
|
15356
|
+
m.id AS message_id,
|
|
15357
|
+
m.space,
|
|
15358
|
+
m.from_agent,
|
|
15359
|
+
m.created_at,
|
|
15360
|
+
m.priority,
|
|
15361
|
+
m.content,
|
|
15362
|
+
m.attachments,
|
|
15363
|
+
s.preview_chars,
|
|
15364
|
+
snr.message_id AS read_message_id
|
|
15365
|
+
FROM messages m
|
|
15366
|
+
INNER JOIN space_subscriptions s
|
|
15367
|
+
ON s.space = m.space
|
|
15368
|
+
LEFT JOIN space_notification_reads snr
|
|
15369
|
+
ON snr.message_id = m.id AND snr.agent = s.agent
|
|
15370
|
+
WHERE ${conditions.join(" AND ")}
|
|
15371
|
+
ORDER BY m.created_at DESC, m.id DESC
|
|
15372
|
+
LIMIT ${Math.max(1, Math.min(limit, 500))}
|
|
15373
|
+
`).all(...params);
|
|
15374
|
+
const notifications = rows.map((row) => ({
|
|
15375
|
+
message_id: row.message_id,
|
|
15376
|
+
space: row.space,
|
|
15377
|
+
from_agent: row.from_agent,
|
|
15378
|
+
created_at: row.created_at,
|
|
15379
|
+
priority: row.priority,
|
|
15380
|
+
preview: buildMessagePreview(row.content, row.preview_chars),
|
|
15381
|
+
unread: row.read_message_id == null,
|
|
15382
|
+
has_attachments: !!row.attachments && row.attachments !== "[]"
|
|
15383
|
+
}));
|
|
15384
|
+
if (opts.mark_read && notifications.length > 0) {
|
|
15385
|
+
markSpaceNotificationsRead(opts.agent, notifications.map((row) => row.message_id));
|
|
15386
|
+
for (const row of notifications)
|
|
15387
|
+
row.unread = false;
|
|
15388
|
+
}
|
|
15389
|
+
return notifications;
|
|
15390
|
+
}
|
|
15391
|
+
function markSpaceNotificationsRead(agent, messageIds) {
|
|
15392
|
+
if (messageIds.length === 0)
|
|
15393
|
+
return 0;
|
|
15394
|
+
const db2 = getDb();
|
|
15395
|
+
const insert = db2.prepare(`
|
|
15396
|
+
INSERT OR IGNORE INTO space_notification_reads (agent, message_id)
|
|
15397
|
+
VALUES (?, ?)
|
|
15398
|
+
`);
|
|
15399
|
+
let count = 0;
|
|
15400
|
+
for (const id of messageIds) {
|
|
15401
|
+
const result = insert.run(agent, id);
|
|
15402
|
+
count += result.changes;
|
|
15403
|
+
}
|
|
15404
|
+
return count;
|
|
15405
|
+
}
|
|
15406
|
+
function markAllSpaceNotificationsRead(agent, space) {
|
|
15407
|
+
const unread = readSpaceNotifications({ agent, space, unread_only: true, limit: 1e4 });
|
|
15408
|
+
return markSpaceNotificationsRead(agent, unread.map((row) => row.message_id));
|
|
15409
|
+
}
|
|
15410
|
+
var DEFAULT_PREVIEW_CHARS = 140;
|
|
15411
|
+
var init_space_notifications = __esm(() => {
|
|
15412
|
+
init_db();
|
|
15413
|
+
});
|
|
15414
|
+
|
|
15264
15415
|
// package.json
|
|
15265
15416
|
var require_package = __commonJS((exports, module) => {
|
|
15266
15417
|
module.exports = {
|
|
15267
15418
|
name: "@hasna/conversations",
|
|
15268
|
-
version: "0.2.
|
|
15419
|
+
version: "0.2.46",
|
|
15269
15420
|
description: "Real-time CLI messaging for AI agents",
|
|
15270
15421
|
type: "module",
|
|
15271
15422
|
bin: {
|
|
@@ -45160,6 +45311,23 @@ function registerMessagingTools(server, resolveProjectId) {
|
|
|
45160
45311
|
content: [{ type: "text", text: JSON.stringify({ messages, count: messages.length, offset: args.offset ?? 0 }) }]
|
|
45161
45312
|
};
|
|
45162
45313
|
});
|
|
45314
|
+
server.registerTool("get_message", {
|
|
45315
|
+
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.",
|
|
45316
|
+
inputSchema: {
|
|
45317
|
+
id: exports_external2.coerce.number().describe("Numeric message ID to fetch")
|
|
45318
|
+
}
|
|
45319
|
+
}, async (args) => {
|
|
45320
|
+
const message = getMessageById(args.id);
|
|
45321
|
+
if (!message) {
|
|
45322
|
+
return {
|
|
45323
|
+
content: [{ type: "text", text: `Message #${args.id} not found` }],
|
|
45324
|
+
isError: true
|
|
45325
|
+
};
|
|
45326
|
+
}
|
|
45327
|
+
return {
|
|
45328
|
+
content: [{ type: "text", text: JSON.stringify(message) }]
|
|
45329
|
+
};
|
|
45330
|
+
});
|
|
45163
45331
|
server.registerTool("list_sessions", {
|
|
45164
45332
|
description: "List all sessions by agent.",
|
|
45165
45333
|
inputSchema: {
|
|
@@ -45554,6 +45722,7 @@ function registerSpaceTools(server) {
|
|
|
45554
45722
|
if (fromParam && messages.length > 0) {
|
|
45555
45723
|
const agent = resolveIdentity(fromParam);
|
|
45556
45724
|
recordReadReceiptsBatch(messages.map((m) => m.id), agent);
|
|
45725
|
+
markSpaceNotificationsRead(agent, messages.map((m) => m.id));
|
|
45557
45726
|
}
|
|
45558
45727
|
return {
|
|
45559
45728
|
content: [{ type: "text", text: JSON.stringify(messages) }]
|
|
@@ -45593,6 +45762,86 @@ function registerSpaceTools(server) {
|
|
|
45593
45762
|
content: [{ type: "text", text: JSON.stringify({ space, agent, left }) }]
|
|
45594
45763
|
};
|
|
45595
45764
|
});
|
|
45765
|
+
server.registerTool("subscribe_space_notifications", {
|
|
45766
|
+
description: "Subscribe an agent to preview-only notifications for a space.",
|
|
45767
|
+
inputSchema: {
|
|
45768
|
+
space: exports_external2.string(),
|
|
45769
|
+
from: exports_external2.string().optional(),
|
|
45770
|
+
preview_chars: exports_external2.coerce.number().optional()
|
|
45771
|
+
}
|
|
45772
|
+
}, async (args) => {
|
|
45773
|
+
const agent = resolveIdentity(args.from);
|
|
45774
|
+
try {
|
|
45775
|
+
const subscription = subscribeToSpaceNotifications(args.space, agent, { preview_chars: args.preview_chars });
|
|
45776
|
+
return { content: [{ type: "text", text: JSON.stringify(subscription) }] };
|
|
45777
|
+
} catch (e) {
|
|
45778
|
+
return { content: [{ type: "text", text: e.message }], isError: true };
|
|
45779
|
+
}
|
|
45780
|
+
});
|
|
45781
|
+
server.registerTool("unsubscribe_space_notifications", {
|
|
45782
|
+
description: "Stop preview-only notifications for a space.",
|
|
45783
|
+
inputSchema: {
|
|
45784
|
+
space: exports_external2.string(),
|
|
45785
|
+
from: exports_external2.string().optional()
|
|
45786
|
+
}
|
|
45787
|
+
}, async (args) => {
|
|
45788
|
+
const agent = resolveIdentity(args.from);
|
|
45789
|
+
const unsubscribed = unsubscribeFromSpaceNotifications(args.space, agent);
|
|
45790
|
+
return { content: [{ type: "text", text: JSON.stringify({ space: args.space, agent, unsubscribed }) }] };
|
|
45791
|
+
});
|
|
45792
|
+
server.registerTool("list_space_subscriptions", {
|
|
45793
|
+
description: "List an agent's preview-only space notification subscriptions.",
|
|
45794
|
+
inputSchema: {
|
|
45795
|
+
from: exports_external2.string().optional(),
|
|
45796
|
+
space: exports_external2.string().optional()
|
|
45797
|
+
}
|
|
45798
|
+
}, async (args) => {
|
|
45799
|
+
const agent = resolveIdentity(args.from);
|
|
45800
|
+
const subscriptions = listSpaceNotificationSubscriptions(agent).filter((row) => !args.space || row.space === args.space);
|
|
45801
|
+
return { content: [{ type: "text", text: JSON.stringify(subscriptions) }] };
|
|
45802
|
+
});
|
|
45803
|
+
server.registerTool("read_space_notifications", {
|
|
45804
|
+
description: "Read preview-only notifications for an agent's subscribed spaces. Returns blurbs, not full message bodies.",
|
|
45805
|
+
inputSchema: {
|
|
45806
|
+
from: exports_external2.string().optional(),
|
|
45807
|
+
space: exports_external2.string().optional(),
|
|
45808
|
+
unread_only: exports_external2.coerce.boolean().optional(),
|
|
45809
|
+
limit: exports_external2.coerce.number().optional(),
|
|
45810
|
+
since: exports_external2.string().optional(),
|
|
45811
|
+
mark_read: exports_external2.coerce.boolean().optional()
|
|
45812
|
+
}
|
|
45813
|
+
}, async (args) => {
|
|
45814
|
+
const agent = resolveIdentity(args.from);
|
|
45815
|
+
const notifications = readSpaceNotifications({
|
|
45816
|
+
agent,
|
|
45817
|
+
space: args.space,
|
|
45818
|
+
unread_only: args.unread_only,
|
|
45819
|
+
limit: args.limit,
|
|
45820
|
+
since: args.since,
|
|
45821
|
+
mark_read: args.mark_read
|
|
45822
|
+
});
|
|
45823
|
+
return { content: [{ type: "text", text: JSON.stringify({ notifications, count: notifications.length }) }] };
|
|
45824
|
+
});
|
|
45825
|
+
server.registerTool("mark_space_notifications_read", {
|
|
45826
|
+
description: "Mark preview-only space notifications as read for an agent.",
|
|
45827
|
+
inputSchema: {
|
|
45828
|
+
from: exports_external2.string().optional(),
|
|
45829
|
+
ids: exports_external2.array(exports_external2.coerce.number()).optional(),
|
|
45830
|
+
space: exports_external2.string().optional(),
|
|
45831
|
+
all: exports_external2.coerce.boolean().optional()
|
|
45832
|
+
}
|
|
45833
|
+
}, async (args) => {
|
|
45834
|
+
const agent = resolveIdentity(args.from);
|
|
45835
|
+
let marked = 0;
|
|
45836
|
+
if (args.all) {
|
|
45837
|
+
marked = markAllSpaceNotificationsRead(agent, args.space);
|
|
45838
|
+
} else if (Array.isArray(args.ids) && args.ids.length > 0) {
|
|
45839
|
+
marked = markSpaceNotificationsRead(agent, args.ids);
|
|
45840
|
+
} else {
|
|
45841
|
+
return { content: [{ type: "text", text: "Provide ids or all=true" }], isError: true };
|
|
45842
|
+
}
|
|
45843
|
+
return { content: [{ type: "text", text: JSON.stringify({ marked_read: marked }) }] };
|
|
45844
|
+
});
|
|
45596
45845
|
server.registerTool("update_space", {
|
|
45597
45846
|
description: "Update space description or parent.",
|
|
45598
45847
|
inputSchema: {
|
|
@@ -45750,6 +45999,7 @@ var init_spaces2 = __esm(() => {
|
|
|
45750
45999
|
init_zod2();
|
|
45751
46000
|
init_messages();
|
|
45752
46001
|
init_spaces();
|
|
46002
|
+
init_space_notifications();
|
|
45753
46003
|
init_identity();
|
|
45754
46004
|
init_messages();
|
|
45755
46005
|
});
|
|
@@ -45981,87 +46231,131 @@ function getSessionAgent() {
|
|
|
45981
46231
|
function getClaudeSessionId() {
|
|
45982
46232
|
return sessionClaudeId || process.env.CONVERSATIONS_SESSION_ID || null;
|
|
45983
46233
|
}
|
|
45984
|
-
function registerChannelBridge(server) {
|
|
46234
|
+
function registerChannelBridge(server, opts) {
|
|
45985
46235
|
server.server.registerCapabilities({
|
|
45986
46236
|
experimental: { "claude/channel": {} }
|
|
45987
46237
|
});
|
|
46238
|
+
const pollIntervalMs = opts?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
46239
|
+
const startDelayMs = opts?.startDelayMs ?? DEFAULT_START_DELAY_MS;
|
|
45988
46240
|
let lastAgentMsgId = 0;
|
|
45989
46241
|
let lastSessionMsgId = 0;
|
|
45990
46242
|
let pollTimer = null;
|
|
46243
|
+
let startTimer = null;
|
|
46244
|
+
let polling = false;
|
|
45991
46245
|
function getSessionId() {
|
|
45992
46246
|
return getClaudeSessionId();
|
|
45993
46247
|
}
|
|
45994
|
-
function
|
|
45995
|
-
const
|
|
45996
|
-
const sid = getSessionId();
|
|
45997
|
-
if (agent) {
|
|
45998
|
-
const latest = readMessages({ to: agent, order: "desc", limit: 1 });
|
|
45999
|
-
if (latest.length > 0)
|
|
46000
|
-
lastAgentMsgId = latest[0].id;
|
|
46001
|
-
}
|
|
46002
|
-
if (sid) {
|
|
46003
|
-
const latest = readMessages({ to: `session:${sid}`, order: "desc", limit: 1 });
|
|
46004
|
-
if (latest.length > 0)
|
|
46005
|
-
lastSessionMsgId = latest[0].id;
|
|
46006
|
-
}
|
|
46007
|
-
}
|
|
46008
|
-
function pushNotification(msg, isDirect) {
|
|
46009
|
-
if (isDirect) {
|
|
46010
|
-
try {
|
|
46011
|
-
markReadByIds([msg.id]);
|
|
46012
|
-
} catch {}
|
|
46013
|
-
}
|
|
46014
|
-
const senderSession = msg.session_id;
|
|
46015
|
-
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.`;
|
|
46016
|
-
const enrichedContent = `${msg.content}
|
|
46248
|
+
async function pushNotification(msg, mode) {
|
|
46249
|
+
const enrichedContent = mode === "space_blurb" ? `${msg.from_agent} posted in #${msg.space}: ${msg.content}
|
|
46017
46250
|
|
|
46018
46251
|
---
|
|
46019
|
-
[
|
|
46020
|
-
|
|
46021
|
-
|
|
46022
|
-
|
|
46023
|
-
|
|
46024
|
-
|
|
46025
|
-
|
|
46026
|
-
|
|
46027
|
-
|
|
46028
|
-
|
|
46029
|
-
|
|
46030
|
-
|
|
46252
|
+
[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}
|
|
46253
|
+
|
|
46254
|
+
---
|
|
46255
|
+
[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.]`;
|
|
46256
|
+
try {
|
|
46257
|
+
await server.server.notification({
|
|
46258
|
+
method: "notifications/claude/channel",
|
|
46259
|
+
params: {
|
|
46260
|
+
content: enrichedContent,
|
|
46261
|
+
meta: {
|
|
46262
|
+
from: msg.from_agent,
|
|
46263
|
+
message_id: String(msg.id),
|
|
46264
|
+
session_id: msg.session_id,
|
|
46265
|
+
mode,
|
|
46266
|
+
...msg.space ? { space: msg.space } : {},
|
|
46267
|
+
...msg.priority && msg.priority !== "normal" ? { priority: msg.priority } : {}
|
|
46268
|
+
}
|
|
46269
|
+
}
|
|
46270
|
+
});
|
|
46271
|
+
if (mode === "direct") {
|
|
46272
|
+
try {
|
|
46273
|
+
markReadByIds([msg.id]);
|
|
46274
|
+
} catch {}
|
|
46275
|
+
} else if (mode === "space_blurb") {
|
|
46276
|
+
const agent = getSessionAgent();
|
|
46277
|
+
if (agent) {
|
|
46278
|
+
try {
|
|
46279
|
+
markSpaceNotificationsRead(agent, [msg.id]);
|
|
46280
|
+
} catch {}
|
|
46031
46281
|
}
|
|
46032
46282
|
}
|
|
46033
|
-
|
|
46283
|
+
return true;
|
|
46284
|
+
} catch {
|
|
46285
|
+
return false;
|
|
46286
|
+
}
|
|
46034
46287
|
}
|
|
46035
|
-
function
|
|
46036
|
-
if (
|
|
46288
|
+
async function pollOnce() {
|
|
46289
|
+
if (polling)
|
|
46037
46290
|
return;
|
|
46038
|
-
|
|
46039
|
-
|
|
46040
|
-
|
|
46041
|
-
|
|
46042
|
-
|
|
46043
|
-
|
|
46044
|
-
|
|
46045
|
-
|
|
46046
|
-
|
|
46047
|
-
|
|
46048
|
-
|
|
46291
|
+
polling = true;
|
|
46292
|
+
try {
|
|
46293
|
+
const agent = getSessionAgent();
|
|
46294
|
+
const sid = getSessionId();
|
|
46295
|
+
if (agent) {
|
|
46296
|
+
const msgs = readMessages({ to: agent, unread_only: true, order: "asc", limit: 20 }).filter((m) => m.id > lastAgentMsgId && m.from_agent !== agent);
|
|
46297
|
+
for (const msg of msgs) {
|
|
46298
|
+
const delivered = await pushNotification(msg, "dm");
|
|
46299
|
+
if (!delivered)
|
|
46300
|
+
break;
|
|
46301
|
+
lastAgentMsgId = msg.id;
|
|
46049
46302
|
}
|
|
46050
|
-
|
|
46051
|
-
|
|
46052
|
-
|
|
46053
|
-
|
|
46054
|
-
|
|
46055
|
-
|
|
46303
|
+
}
|
|
46304
|
+
if (sid) {
|
|
46305
|
+
const msgs = readMessages({ to: `session:${sid}`, unread_only: true, order: "asc", limit: 20 }).filter((m) => m.id > lastSessionMsgId && m.from_agent !== agent);
|
|
46306
|
+
for (const msg of msgs) {
|
|
46307
|
+
const delivered = await pushNotification(msg, "direct");
|
|
46308
|
+
if (!delivered)
|
|
46309
|
+
break;
|
|
46310
|
+
lastSessionMsgId = msg.id;
|
|
46056
46311
|
}
|
|
46057
|
-
}
|
|
46058
|
-
|
|
46312
|
+
}
|
|
46313
|
+
if (agent) {
|
|
46314
|
+
const notifications = readSpaceNotifications({
|
|
46315
|
+
agent,
|
|
46316
|
+
unread_only: true,
|
|
46317
|
+
limit: 20,
|
|
46318
|
+
mark_read: false
|
|
46319
|
+
}).sort((left, right) => left.created_at.localeCompare(right.created_at) || left.message_id - right.message_id);
|
|
46320
|
+
for (const notification of notifications) {
|
|
46321
|
+
const delivered = await pushNotification({
|
|
46322
|
+
id: notification.message_id,
|
|
46323
|
+
content: notification.preview,
|
|
46324
|
+
from_agent: notification.from_agent,
|
|
46325
|
+
session_id: `space:${notification.space}`,
|
|
46326
|
+
space: notification.space,
|
|
46327
|
+
priority: notification.priority
|
|
46328
|
+
}, "space_blurb");
|
|
46329
|
+
if (!delivered)
|
|
46330
|
+
break;
|
|
46331
|
+
}
|
|
46332
|
+
}
|
|
46333
|
+
} finally {
|
|
46334
|
+
polling = false;
|
|
46335
|
+
}
|
|
46059
46336
|
}
|
|
46060
|
-
|
|
46337
|
+
function startPolling2() {
|
|
46338
|
+
if (pollTimer)
|
|
46339
|
+
return;
|
|
46340
|
+
pollTimer = setInterval(() => {
|
|
46341
|
+
pollOnce().catch(() => {});
|
|
46342
|
+
}, pollIntervalMs);
|
|
46343
|
+
pollOnce().catch(() => {});
|
|
46344
|
+
}
|
|
46345
|
+
startTimer = setTimeout(() => startPolling2(), startDelayMs);
|
|
46346
|
+
return () => {
|
|
46347
|
+
if (startTimer)
|
|
46348
|
+
clearTimeout(startTimer);
|
|
46349
|
+
if (pollTimer)
|
|
46350
|
+
clearInterval(pollTimer);
|
|
46351
|
+
startTimer = null;
|
|
46352
|
+
pollTimer = null;
|
|
46353
|
+
};
|
|
46061
46354
|
}
|
|
46062
|
-
var
|
|
46355
|
+
var DEFAULT_POLL_INTERVAL_MS = 1000, DEFAULT_START_DELAY_MS = 2000, sessionAgentId = null, sessionClaudeId = null;
|
|
46063
46356
|
var init_channel = __esm(() => {
|
|
46064
46357
|
init_messages();
|
|
46358
|
+
init_space_notifications();
|
|
46065
46359
|
});
|
|
46066
46360
|
|
|
46067
46361
|
// src/mcp/tools/agents.ts
|
|
@@ -46784,6 +47078,7 @@ function registerAdvancedTools(server, pkgVersion) {
|
|
|
46784
47078
|
const all = [
|
|
46785
47079
|
"send_message",
|
|
46786
47080
|
"read_messages",
|
|
47081
|
+
"get_message",
|
|
46787
47082
|
"read_digest",
|
|
46788
47083
|
"list_sessions",
|
|
46789
47084
|
"reply",
|
|
@@ -46799,6 +47094,11 @@ function registerAdvancedTools(server, pkgVersion) {
|
|
|
46799
47094
|
"update_space",
|
|
46800
47095
|
"archive_space",
|
|
46801
47096
|
"unarchive_space",
|
|
47097
|
+
"subscribe_space_notifications",
|
|
47098
|
+
"unsubscribe_space_notifications",
|
|
47099
|
+
"list_space_subscriptions",
|
|
47100
|
+
"read_space_notifications",
|
|
47101
|
+
"mark_space_notifications_read",
|
|
46802
47102
|
"create_project",
|
|
46803
47103
|
"list_projects",
|
|
46804
47104
|
"get_project",
|
|
@@ -46854,6 +47154,7 @@ function registerAdvancedTools(server, pkgVersion) {
|
|
|
46854
47154
|
const descriptions = {
|
|
46855
47155
|
send_message: "Send DM to agent. Required: to, content. Optional: from?, priority?(low|normal|high|urgent), blocking?",
|
|
46856
47156
|
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)",
|
|
47157
|
+
get_message: "Get the full content of a specific message by id. Required: id",
|
|
46857
47158
|
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?",
|
|
46858
47159
|
list_sessions: "List all DM sessions. Optional: agent?(filter by participant)",
|
|
46859
47160
|
reply: "Reply to a specific message, creating a thread (sets reply_to). Use read_thread to retrieve. Required: message_id, content. Optional: from?",
|
|
@@ -46871,6 +47172,11 @@ function registerAdvancedTools(server, pkgVersion) {
|
|
|
46871
47172
|
update_space: "Update space fields. Required: name. Optional: description?, parent_id?(use 'null' to remove), project_id?(use 'null' to remove)",
|
|
46872
47173
|
archive_space: "Archive a space (hidden from default list). Required: name",
|
|
46873
47174
|
unarchive_space: "Restore archived space. Required: name",
|
|
47175
|
+
subscribe_space_notifications: "Subscribe to preview-only notifications for a space. Required: space. Optional: from?, preview_chars?",
|
|
47176
|
+
unsubscribe_space_notifications: "Stop preview-only notifications for a space. Required: space. Optional: from?",
|
|
47177
|
+
list_space_subscriptions: "List preview-only space notification subscriptions for the current agent. Optional: from?, space?",
|
|
47178
|
+
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?",
|
|
47179
|
+
mark_space_notifications_read: "Mark preview-only space notifications as read. Optional: from?, ids?(array), space?, all?(bool)",
|
|
46874
47180
|
create_project: "Create a project. Required: name. Optional: from?, description?, path?, repository?, tags?(JSON array), metadata?(JSON), settings?(JSON)",
|
|
46875
47181
|
list_projects: "List projects. Optional: status?(active|archived)",
|
|
46876
47182
|
get_project: "Get project by UUID or name. Required: id",
|
|
@@ -46987,6 +47293,24 @@ var init_pg_migrations = __esm(() => {
|
|
|
46987
47293
|
PRIMARY KEY (space, agent)
|
|
46988
47294
|
);
|
|
46989
47295
|
|
|
47296
|
+
CREATE TABLE IF NOT EXISTS space_subscriptions (
|
|
47297
|
+
space TEXT NOT NULL REFERENCES spaces(name),
|
|
47298
|
+
agent TEXT NOT NULL,
|
|
47299
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
47300
|
+
preview_chars INTEGER NOT NULL DEFAULT 140,
|
|
47301
|
+
since_message_id BIGINT NOT NULL DEFAULT 0,
|
|
47302
|
+
PRIMARY KEY (space, agent)
|
|
47303
|
+
);
|
|
47304
|
+
CREATE INDEX IF NOT EXISTS idx_space_subscriptions_agent ON space_subscriptions(agent);
|
|
47305
|
+
CREATE INDEX IF NOT EXISTS idx_space_subscriptions_space ON space_subscriptions(space);
|
|
47306
|
+
ALTER TABLE space_subscriptions ADD COLUMN IF NOT EXISTS since_message_id BIGINT NOT NULL DEFAULT 0;
|
|
47307
|
+
UPDATE space_subscriptions ss
|
|
47308
|
+
SET since_message_id = COALESCE(
|
|
47309
|
+
(SELECT MAX(m.id) FROM messages m WHERE m.space = ss.space),
|
|
47310
|
+
0
|
|
47311
|
+
)
|
|
47312
|
+
WHERE ss.since_message_id = 0;
|
|
47313
|
+
|
|
46990
47314
|
CREATE TABLE IF NOT EXISTS messages (
|
|
46991
47315
|
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
46992
47316
|
uuid TEXT NOT NULL DEFAULT gen_random_uuid()::text UNIQUE,
|
|
@@ -47064,6 +47388,15 @@ var init_pg_migrations = __esm(() => {
|
|
|
47064
47388
|
CREATE INDEX IF NOT EXISTS idx_read_receipts_message ON message_read_receipts(message_id);
|
|
47065
47389
|
CREATE INDEX IF NOT EXISTS idx_read_receipts_agent ON message_read_receipts(agent);
|
|
47066
47390
|
|
|
47391
|
+
CREATE TABLE IF NOT EXISTS space_notification_reads (
|
|
47392
|
+
agent TEXT NOT NULL,
|
|
47393
|
+
message_id BIGINT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
47394
|
+
read_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
47395
|
+
PRIMARY KEY (agent, message_id)
|
|
47396
|
+
);
|
|
47397
|
+
CREATE INDEX IF NOT EXISTS idx_space_notification_reads_agent ON space_notification_reads(agent);
|
|
47398
|
+
CREATE INDEX IF NOT EXISTS idx_space_notification_reads_message ON space_notification_reads(message_id);
|
|
47399
|
+
|
|
47067
47400
|
CREATE TABLE IF NOT EXISTS message_mentions (
|
|
47068
47401
|
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
47069
47402
|
message_id BIGINT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
@@ -47497,11 +47830,11 @@ ${msg.text}`;
|
|
|
47497
47830
|
} catch {}
|
|
47498
47831
|
}
|
|
47499
47832
|
setTimeout(() => {
|
|
47500
|
-
pollTimer = setInterval(() => poll(),
|
|
47833
|
+
pollTimer = setInterval(() => poll(), POLL_INTERVAL_MS);
|
|
47501
47834
|
console.error("[telegram-channel] polling started");
|
|
47502
47835
|
}, 2000);
|
|
47503
47836
|
}
|
|
47504
|
-
var
|
|
47837
|
+
var POLL_INTERVAL_MS = 2000;
|
|
47505
47838
|
var init_telegram_channel = __esm(() => {
|
|
47506
47839
|
init_zod2();
|
|
47507
47840
|
});
|
|
@@ -49362,7 +49695,7 @@ init_db();
|
|
|
49362
49695
|
init_identity();
|
|
49363
49696
|
init_terminal_markdown();
|
|
49364
49697
|
init_presence();
|
|
49365
|
-
|
|
49698
|
+
init_space_notifications();
|
|
49366
49699
|
import chalk4 from "chalk";
|
|
49367
49700
|
function registerMessagingCommands(program2) {
|
|
49368
49701
|
program2.command("send").description("Send a message to an agent").argument("<message>", "Message content").option("--to <agent>", "Recipient agent ID (required unless --space is used)").option("--from <agent>", "Sender agent ID").option("--session <id>", "Session ID (auto-generated if omitted)").option("--priority <level>", "Priority: low, normal, high, urgent", "normal").option("--working-dir <path>", "Working directory context").option("--repository <repo>", "Repository context").option("--branch <branch>", "Branch context").option("--metadata <json>", "JSON metadata string").option("--space <name>", "Send to a space instead of a specific agent").option("--blocking", "Send as a blocking message (recipient must acknowledge)").option("-j, --json", "Output as JSON").action((message, opts) => {
|
|
@@ -49459,6 +49792,32 @@ function registerMessagingCommands(program2) {
|
|
|
49459
49792
|
}
|
|
49460
49793
|
closeDb();
|
|
49461
49794
|
});
|
|
49795
|
+
program2.command("show").description("Show a full message by ID").argument("<id>", "Numeric message ID").option("-j, --json", "Output as JSON").action((idArg, opts) => {
|
|
49796
|
+
const id = Number.parseInt(String(idArg), 10);
|
|
49797
|
+
if (!Number.isFinite(id) || id <= 0) {
|
|
49798
|
+
console.error(chalk4.red("Message ID must be a positive integer."));
|
|
49799
|
+
process.exit(1);
|
|
49800
|
+
}
|
|
49801
|
+
const msg = getMessageById(id);
|
|
49802
|
+
if (!msg) {
|
|
49803
|
+
console.error(chalk4.red(`Message #${id} not found.`));
|
|
49804
|
+
process.exit(1);
|
|
49805
|
+
}
|
|
49806
|
+
if (opts.json) {
|
|
49807
|
+
console.log(JSON.stringify(msg, null, 2));
|
|
49808
|
+
} else {
|
|
49809
|
+
const time = chalk4.dim(msg.created_at.slice(0, 19).replace("T", " "));
|
|
49810
|
+
const destination = msg.space ? chalk4.magenta(`#${msg.space}`) : chalk4.yellow(msg.to_agent);
|
|
49811
|
+
const priority = msg.priority !== "normal" ? chalk4.red(` [${msg.priority}]`) : "";
|
|
49812
|
+
const unread = !msg.read_at ? chalk4.green(" [unread]") : "";
|
|
49813
|
+
console.log(`${chalk4.cyan(msg.from_agent)} \u2192 ${destination}${priority}${unread} ${chalk4.dim(`[#${msg.id}] ${time}`)}`);
|
|
49814
|
+
if (msg.attachments?.length) {
|
|
49815
|
+
console.log(chalk4.dim(`Attachments: ${msg.attachments.map((att) => att.name).join(", ")}`));
|
|
49816
|
+
}
|
|
49817
|
+
console.log(renderContent(msg.content));
|
|
49818
|
+
}
|
|
49819
|
+
closeDb();
|
|
49820
|
+
});
|
|
49462
49821
|
program2.command("digest").description("Show unread message digest (preview only, auto-marks read)").argument("[space]", "Space name to digest (omit for DMs)").option("--since <timestamp>", "Messages after this ISO timestamp").option("--limit <n>", "Max messages to show", parseInt).option("--to <agent>", "Filter by recipient (for DMs)").option("-j, --json", "Output as JSON").action((spaceArg, opts) => {
|
|
49463
49822
|
const result = readDigest({
|
|
49464
49823
|
space: spaceArg || undefined,
|
|
@@ -49734,6 +50093,44 @@ Acknowledge with: conversations mark-read ${blockers.map((b) => b.id).join(" ")}
|
|
|
49734
50093
|
}
|
|
49735
50094
|
closeDb();
|
|
49736
50095
|
});
|
|
50096
|
+
program2.command("notifications").description("List preview-only notifications from subscribed spaces").option("--from <agent>", "Your agent identity").option("--space <name>", "Filter to a single space").option("--since <timestamp>", "Notifications after this ISO timestamp").option("--limit <n>", "Max notifications to return", parseInt).option("--all", "Include already-read notifications").option("--mark-read", "Mark returned notifications as read").option("--clear", "Mark all matching unread notifications as read without listing").option("-j, --json", "Output as JSON").action((opts) => {
|
|
50097
|
+
const agent = resolveIdentity(opts.from);
|
|
50098
|
+
heartbeat(agent);
|
|
50099
|
+
if (opts.clear) {
|
|
50100
|
+
const cleared = markAllSpaceNotificationsRead(agent, opts.space);
|
|
50101
|
+
if (opts.json) {
|
|
50102
|
+
console.log(JSON.stringify({ cleared, agent, space: opts.space || null }, null, 2));
|
|
50103
|
+
} else {
|
|
50104
|
+
console.log(chalk4.green(`Cleared ${cleared} notification(s).`));
|
|
50105
|
+
}
|
|
50106
|
+
closeDb();
|
|
50107
|
+
return;
|
|
50108
|
+
}
|
|
50109
|
+
const notifications = readSpaceNotifications({
|
|
50110
|
+
agent,
|
|
50111
|
+
space: opts.space,
|
|
50112
|
+
since: opts.since,
|
|
50113
|
+
unread_only: !opts.all,
|
|
50114
|
+
limit: opts.limit,
|
|
50115
|
+
mark_read: opts.markRead
|
|
50116
|
+
});
|
|
50117
|
+
if (opts.json) {
|
|
50118
|
+
console.log(JSON.stringify(notifications, null, 2));
|
|
50119
|
+
} else if (notifications.length === 0) {
|
|
50120
|
+
console.log(chalk4.dim("No space notifications."));
|
|
50121
|
+
} else {
|
|
50122
|
+
for (const item of notifications) {
|
|
50123
|
+
const time = chalk4.dim(item.created_at.slice(11, 19));
|
|
50124
|
+
const priority = item.priority !== "normal" ? chalk4.red(` [${item.priority}]`) : "";
|
|
50125
|
+
const unread = item.unread ? chalk4.yellow(" [unread]") : "";
|
|
50126
|
+
console.log(`${time} ${chalk4.cyan(item.from_agent)} ${chalk4.magenta(`#${item.space}`)}${priority}${unread} ${chalk4.dim(`msg #${item.message_id}`)}`);
|
|
50127
|
+
console.log(` ${item.preview}`);
|
|
50128
|
+
}
|
|
50129
|
+
console.log(chalk4.dim(`
|
|
50130
|
+
Inspect the full message later with: conversations show <message-id>`));
|
|
50131
|
+
}
|
|
50132
|
+
closeDb();
|
|
50133
|
+
});
|
|
49737
50134
|
program2.command("watch").description("Watch for new messages with desktop notifications").option("--from <agent>", "Your agent identity").option("--space <name>", "Watch a specific space").option("--all", "Watch DMs and all subscribed spaces").option("--interval <ms>", "Poll interval in milliseconds", parseInt).action((opts) => {
|
|
49738
50135
|
const agent = resolveIdentity(opts.from);
|
|
49739
50136
|
heartbeat(agent);
|
|
@@ -49741,9 +50138,7 @@ Acknowledge with: conversations mark-read ${blockers.map((b) => b.id).join(" ")}
|
|
|
49741
50138
|
const cols = Math.min(process.stdout.columns || 80, 100);
|
|
49742
50139
|
let agentSpaces = [];
|
|
49743
50140
|
if (opts.all) {
|
|
49744
|
-
|
|
49745
|
-
const rows = db2.prepare("SELECT space FROM space_members WHERE agent = ?").all(agent);
|
|
49746
|
-
agentSpaces = rows.map((r) => r.space);
|
|
50141
|
+
agentSpaces = listSpaceNotificationSubscriptions(agent).map((row) => row.space);
|
|
49747
50142
|
}
|
|
49748
50143
|
const modeLabel = opts.all ? `DMs + ${agentSpaces.length} space(s)` : opts.space ? `Space: #${opts.space}` : "All DMs";
|
|
49749
50144
|
console.log("");
|
|
@@ -49778,19 +50173,39 @@ Acknowledge with: conversations mark-read ${blockers.map((b) => b.id).join(" ")}
|
|
|
49778
50173
|
console.log(chalk4.dim(" " + "\xB7".repeat(Math.min(cols - 8, 60))));
|
|
49779
50174
|
console.log("");
|
|
49780
50175
|
};
|
|
50176
|
+
const renderNotification = (notification) => {
|
|
50177
|
+
const time = chalk4.dim(notification.created_at.slice(11, 19));
|
|
50178
|
+
const priority = notification.priority !== "normal" ? notification.priority === "urgent" ? chalk4.red.bold(` [${notification.priority}]`) : notification.priority === "high" ? chalk4.yellow(` [${notification.priority}]`) : chalk4.dim(` [${notification.priority}]`) : "";
|
|
50179
|
+
const sender = chalk4.cyan.bold(notification.from_agent);
|
|
50180
|
+
console.log(` ${sender} ${chalk4.magenta(`#${notification.space}`)} ${time}${priority} ${chalk4.dim(`[#${notification.message_id}]`)}`);
|
|
50181
|
+
console.log(` ${notification.preview}`);
|
|
50182
|
+
console.log(chalk4.dim(` Preview only. Inspect with: conversations show ${notification.message_id}`));
|
|
50183
|
+
console.log(chalk4.dim(" " + "\xB7".repeat(Math.min(cols - 8, 60))));
|
|
50184
|
+
console.log("");
|
|
50185
|
+
};
|
|
49781
50186
|
if (opts.all) {
|
|
49782
50187
|
const dmRecent = readMessages({ to: agent, limit: 20, order: "asc" });
|
|
49783
|
-
const
|
|
49784
|
-
|
|
49785
|
-
|
|
49786
|
-
|
|
49787
|
-
|
|
49788
|
-
|
|
49789
|
-
|
|
50188
|
+
const pendingNotifications = readSpaceNotifications({
|
|
50189
|
+
agent,
|
|
50190
|
+
unread_only: true,
|
|
50191
|
+
limit: 20,
|
|
50192
|
+
mark_read: true
|
|
50193
|
+
}).sort((left, right) => left.created_at.localeCompare(right.created_at) || left.message_id - right.message_id);
|
|
50194
|
+
if (dmRecent.length > 0) {
|
|
50195
|
+
console.log(chalk4.dim(` \u2500\u2500 Recent DMs (${dmRecent.length}) \u2500\u2500
|
|
49790
50196
|
`));
|
|
49791
|
-
for (const msg of
|
|
50197
|
+
for (const msg of dmRecent) {
|
|
49792
50198
|
renderMessage(msg);
|
|
49793
50199
|
}
|
|
50200
|
+
}
|
|
50201
|
+
if (pendingNotifications.length > 0) {
|
|
50202
|
+
console.log(chalk4.dim(` \u2500\u2500 Pending space notifications (${pendingNotifications.length}) \u2500\u2500
|
|
50203
|
+
`));
|
|
50204
|
+
for (const notification of pendingNotifications) {
|
|
50205
|
+
renderNotification(notification);
|
|
50206
|
+
}
|
|
50207
|
+
}
|
|
50208
|
+
if (dmRecent.length > 0 || pendingNotifications.length > 0) {
|
|
49794
50209
|
console.log(chalk4.dim(` \u2500\u2500 Live \u2500\u2500
|
|
49795
50210
|
`));
|
|
49796
50211
|
}
|
|
@@ -49817,24 +50232,50 @@ Acknowledge with: conversations mark-read ${blockers.map((b) => b.id).join(" ")}
|
|
|
49817
50232
|
continue;
|
|
49818
50233
|
renderMessage(msg);
|
|
49819
50234
|
const where = msg.space ? `#${msg.space}` : "DM";
|
|
49820
|
-
const preview = msg.content
|
|
50235
|
+
const preview = buildMessagePreview(msg.content, 150);
|
|
49821
50236
|
desktopNotify(`${msg.from_agent} (${where})`, preview);
|
|
49822
50237
|
}
|
|
49823
50238
|
};
|
|
49824
|
-
|
|
49825
|
-
|
|
49826
|
-
|
|
49827
|
-
|
|
50239
|
+
const onNewNotifications = (notifications) => {
|
|
50240
|
+
for (const notification of notifications) {
|
|
50241
|
+
renderNotification(notification);
|
|
50242
|
+
desktopNotify(`${notification.from_agent} (#${notification.space})`, notification.preview);
|
|
49828
50243
|
}
|
|
50244
|
+
};
|
|
50245
|
+
const stops = [];
|
|
50246
|
+
if (opts.all) {
|
|
50247
|
+
stops.push(startPolling2({ to_agent: agent, interval_ms: interval, on_messages: onNewMessages }));
|
|
50248
|
+
let inFlightNotifications = false;
|
|
50249
|
+
const timer = setInterval(() => {
|
|
50250
|
+
if (inFlightNotifications)
|
|
50251
|
+
return;
|
|
50252
|
+
inFlightNotifications = true;
|
|
50253
|
+
try {
|
|
50254
|
+
const notifications = readSpaceNotifications({
|
|
50255
|
+
agent,
|
|
50256
|
+
unread_only: true,
|
|
50257
|
+
limit: 200,
|
|
50258
|
+
mark_read: true
|
|
50259
|
+
}).sort((left, right) => left.created_at.localeCompare(right.created_at) || left.message_id - right.message_id);
|
|
50260
|
+
if (notifications.length > 0) {
|
|
50261
|
+
onNewNotifications(notifications);
|
|
50262
|
+
}
|
|
50263
|
+
} finally {
|
|
50264
|
+
inFlightNotifications = false;
|
|
50265
|
+
}
|
|
50266
|
+
}, interval);
|
|
50267
|
+
stops.push({ stop: () => clearInterval(timer) });
|
|
49829
50268
|
} else {
|
|
49830
|
-
startPolling2({
|
|
50269
|
+
stops.push(startPolling2({
|
|
49831
50270
|
to_agent: opts.space ? undefined : agent,
|
|
49832
50271
|
space: opts.space,
|
|
49833
50272
|
interval_ms: interval,
|
|
49834
50273
|
on_messages: onNewMessages
|
|
49835
|
-
});
|
|
50274
|
+
}));
|
|
49836
50275
|
}
|
|
49837
50276
|
process.on("SIGINT", () => {
|
|
50277
|
+
for (const stop of stops)
|
|
50278
|
+
stop.stop();
|
|
49838
50279
|
console.log(chalk4.dim(`
|
|
49839
50280
|
Stopped watching.`));
|
|
49840
50281
|
closeDb();
|
|
@@ -49900,6 +50341,7 @@ Update failed (exit code ${exitCode})`));
|
|
|
49900
50341
|
// src/cli/commands/spaces.ts
|
|
49901
50342
|
init_messages();
|
|
49902
50343
|
init_spaces();
|
|
50344
|
+
init_space_notifications();
|
|
49903
50345
|
init_db();
|
|
49904
50346
|
init_identity();
|
|
49905
50347
|
init_terminal_markdown();
|
|
@@ -50067,7 +50509,7 @@ function registerSpaceCommands(program2) {
|
|
|
50067
50509
|
}
|
|
50068
50510
|
closeDb();
|
|
50069
50511
|
});
|
|
50070
|
-
space.command("read").description("Read messages from a space").argument("<space>", "Space name").option("--since <timestamp>", "Messages after this ISO timestamp").option("--limit <n>", "Max messages to return", parseInt).option("-j, --json", "Output as JSON").action((spaceName, opts) => {
|
|
50512
|
+
space.command("read").description("Read messages from a space").argument("<space>", "Space name").option("--from <agent>", "Agent reading the space").option("--since <timestamp>", "Messages after this ISO timestamp").option("--limit <n>", "Max messages to return", parseInt).option("-j, --json", "Output as JSON").action((spaceName, opts) => {
|
|
50071
50513
|
const spaceArg = typeof spaceName === "string" ? spaceName.trim() : "";
|
|
50072
50514
|
if (!spaceArg) {
|
|
50073
50515
|
console.error(chalk5.red("Space name cannot be empty."));
|
|
@@ -50078,6 +50520,15 @@ function registerSpaceCommands(program2) {
|
|
|
50078
50520
|
since: opts.since,
|
|
50079
50521
|
limit: opts.limit
|
|
50080
50522
|
});
|
|
50523
|
+
if (opts.from && messages.length > 0) {
|
|
50524
|
+
const agent = resolveIdentity(opts.from).trim();
|
|
50525
|
+
if (!agent) {
|
|
50526
|
+
console.error(chalk5.red("Agent identity is required."));
|
|
50527
|
+
process.exit(1);
|
|
50528
|
+
}
|
|
50529
|
+
recordReadReceiptsBatch(messages.map((m) => m.id), agent);
|
|
50530
|
+
markSpaceNotificationsRead(agent, messages.map((m) => m.id));
|
|
50531
|
+
}
|
|
50081
50532
|
if (opts.json) {
|
|
50082
50533
|
console.log(JSON.stringify(messages, null, 2));
|
|
50083
50534
|
} else {
|
|
@@ -50145,6 +50596,73 @@ function registerSpaceCommands(program2) {
|
|
|
50145
50596
|
}
|
|
50146
50597
|
closeDb();
|
|
50147
50598
|
});
|
|
50599
|
+
space.command("subscribe").description("Subscribe to preview-only notifications for a space").argument("<space>", "Space name").option("--from <agent>", "Agent ID").option("--preview-chars <n>", "Preview length", parseInt).option("-j, --json", "Output as JSON").action((spaceName, opts) => {
|
|
50600
|
+
const agent = resolveIdentity(opts.from).trim();
|
|
50601
|
+
const spaceArg = typeof spaceName === "string" ? spaceName.trim() : "";
|
|
50602
|
+
if (!agent) {
|
|
50603
|
+
console.error(chalk5.red("Agent identity is required."));
|
|
50604
|
+
process.exit(1);
|
|
50605
|
+
}
|
|
50606
|
+
if (!spaceArg) {
|
|
50607
|
+
console.error(chalk5.red("Space name cannot be empty."));
|
|
50608
|
+
process.exit(1);
|
|
50609
|
+
}
|
|
50610
|
+
try {
|
|
50611
|
+
const subscription = subscribeToSpaceNotifications(spaceArg, agent, { preview_chars: opts.previewChars });
|
|
50612
|
+
if (opts.json) {
|
|
50613
|
+
console.log(JSON.stringify(subscription, null, 2));
|
|
50614
|
+
} else {
|
|
50615
|
+
console.log(chalk5.green(`${agent} subscribed to #${spaceArg} notifications`) + chalk5.dim(` (${subscription.preview_chars} chars)`));
|
|
50616
|
+
}
|
|
50617
|
+
} catch (e) {
|
|
50618
|
+
console.error(chalk5.red(e.message));
|
|
50619
|
+
process.exit(1);
|
|
50620
|
+
}
|
|
50621
|
+
closeDb();
|
|
50622
|
+
});
|
|
50623
|
+
space.command("unsubscribe").description("Stop preview-only notifications for a space").argument("<space>", "Space name").option("--from <agent>", "Agent ID").option("-j, --json", "Output as JSON").action((spaceName, opts) => {
|
|
50624
|
+
const agent = resolveIdentity(opts.from).trim();
|
|
50625
|
+
const spaceArg = typeof spaceName === "string" ? spaceName.trim() : "";
|
|
50626
|
+
if (!agent) {
|
|
50627
|
+
console.error(chalk5.red("Agent identity is required."));
|
|
50628
|
+
process.exit(1);
|
|
50629
|
+
}
|
|
50630
|
+
if (!spaceArg) {
|
|
50631
|
+
console.error(chalk5.red("Space name cannot be empty."));
|
|
50632
|
+
process.exit(1);
|
|
50633
|
+
}
|
|
50634
|
+
const unsubscribed = unsubscribeFromSpaceNotifications(spaceArg, agent);
|
|
50635
|
+
if (opts.json) {
|
|
50636
|
+
console.log(JSON.stringify({ space: spaceArg, agent, unsubscribed }));
|
|
50637
|
+
} else if (unsubscribed) {
|
|
50638
|
+
console.log(chalk5.green(`${agent} unsubscribed from #${spaceArg} notifications`));
|
|
50639
|
+
} else {
|
|
50640
|
+
console.log(chalk5.dim(`${agent} had no notification subscription for #${spaceArg}`));
|
|
50641
|
+
}
|
|
50642
|
+
closeDb();
|
|
50643
|
+
});
|
|
50644
|
+
space.command("subscriptions").description("List preview-only space notification subscriptions").option("--from <agent>", "Agent ID").option("--space <name>", "Filter by space").option("-j, --json", "Output as JSON").action((opts) => {
|
|
50645
|
+
const agent = resolveIdentity(opts.from).trim();
|
|
50646
|
+
if (!agent) {
|
|
50647
|
+
console.error(chalk5.red("Agent identity is required."));
|
|
50648
|
+
process.exit(1);
|
|
50649
|
+
}
|
|
50650
|
+
let subscriptions = listSpaceNotificationSubscriptions(agent);
|
|
50651
|
+
if (opts.space) {
|
|
50652
|
+
subscriptions = subscriptions.filter((row) => row.space === opts.space);
|
|
50653
|
+
}
|
|
50654
|
+
if (opts.json) {
|
|
50655
|
+
console.log(JSON.stringify(subscriptions, null, 2));
|
|
50656
|
+
} else if (subscriptions.length === 0) {
|
|
50657
|
+
console.log(chalk5.dim(`No notification subscriptions for ${agent}.`));
|
|
50658
|
+
} else {
|
|
50659
|
+
console.log(chalk5.bold(`${agent} notification subscriptions:`));
|
|
50660
|
+
for (const row of subscriptions) {
|
|
50661
|
+
console.log(` ${chalk5.magenta(`#${row.space}`)} ${chalk5.dim(`preview ${row.preview_chars} chars`)}`);
|
|
50662
|
+
}
|
|
50663
|
+
}
|
|
50664
|
+
closeDb();
|
|
50665
|
+
});
|
|
50148
50666
|
space.command("members").description("List space members").argument("<space>", "Space name").option("-j, --json", "Output as JSON").action((spaceName, opts) => {
|
|
50149
50667
|
const spaceArg = typeof spaceName === "string" ? spaceName.trim() : "";
|
|
50150
50668
|
if (!spaceArg) {
|
|
@@ -50573,6 +51091,7 @@ init_hot();
|
|
|
50573
51091
|
init_topics();
|
|
50574
51092
|
init_summary();
|
|
50575
51093
|
init_graph();
|
|
51094
|
+
init_space_notifications();
|
|
50576
51095
|
var import__package = __toESM(require_package(), 1);
|
|
50577
51096
|
import chalk8 from "chalk";
|
|
50578
51097
|
function registerAnalyticsCommands(program2) {
|
|
@@ -50726,8 +51245,22 @@ function registerAnalyticsCommands(program2) {
|
|
|
50726
51245
|
WHERE sm.agent = ?
|
|
50727
51246
|
ORDER BY s.name
|
|
50728
51247
|
`).all(agent);
|
|
51248
|
+
const subscriptions = listSpaceNotificationSubscriptions(agent);
|
|
51249
|
+
const spaceNotifications = readSpaceNotifications({
|
|
51250
|
+
agent,
|
|
51251
|
+
unread_only: true,
|
|
51252
|
+
limit: 5
|
|
51253
|
+
});
|
|
50729
51254
|
const recentDMs = readMessages({ to: agent, limit: 3 });
|
|
50730
|
-
const context = {
|
|
51255
|
+
const context = {
|
|
51256
|
+
agent,
|
|
51257
|
+
online_agents: onlineAgents,
|
|
51258
|
+
unread_dms: unreadDMs,
|
|
51259
|
+
spaces: mySpaces,
|
|
51260
|
+
space_subscriptions: subscriptions,
|
|
51261
|
+
space_notifications: spaceNotifications,
|
|
51262
|
+
recent_dms: recentDMs
|
|
51263
|
+
};
|
|
50731
51264
|
if (opts.json) {
|
|
50732
51265
|
console.log(JSON.stringify(context, null, 2));
|
|
50733
51266
|
} else {
|
|
@@ -50756,6 +51289,24 @@ function registerAnalyticsCommands(program2) {
|
|
|
50756
51289
|
} else {
|
|
50757
51290
|
console.log(`${chalk8.bold("My spaces:")} ${chalk8.dim("none")}`);
|
|
50758
51291
|
}
|
|
51292
|
+
if (subscriptions.length > 0) {
|
|
51293
|
+
console.log(`${chalk8.bold("Subscribed spaces:")}`);
|
|
51294
|
+
for (const row of subscriptions) {
|
|
51295
|
+
console.log(` ${chalk8.magenta("#" + row.space)} ${chalk8.dim(`preview ${row.preview_chars} chars`)}`);
|
|
51296
|
+
}
|
|
51297
|
+
} else {
|
|
51298
|
+
console.log(`${chalk8.bold("Subscribed spaces:")} ${chalk8.dim("none")}`);
|
|
51299
|
+
}
|
|
51300
|
+
if (spaceNotifications.length > 0) {
|
|
51301
|
+
console.log(`${chalk8.bold("Space notifications:")}`);
|
|
51302
|
+
for (const notification of spaceNotifications) {
|
|
51303
|
+
console.log(` ${chalk8.dim(notification.created_at.slice(11, 16))} ${chalk8.cyan(notification.from_agent)} ${chalk8.magenta("#" + notification.space)} ${chalk8.dim(`msg #${notification.message_id}`)}`);
|
|
51304
|
+
console.log(` ${chalk8.dim(notification.preview)}`);
|
|
51305
|
+
}
|
|
51306
|
+
console.log(chalk8.dim(" Inspect with: conversations show <message-id>"));
|
|
51307
|
+
} else {
|
|
51308
|
+
console.log(`${chalk8.bold("Space notifications:")} ${chalk8.dim("none")}`);
|
|
51309
|
+
}
|
|
50759
51310
|
}
|
|
50760
51311
|
closeDb();
|
|
50761
51312
|
});
|