@hasna/conversations 0.2.0 → 0.2.2
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 +18 -8
- package/bin/index.js +434 -26
- package/bin/mcp.js +318 -23
- package/dist/index.d.ts +2 -2
- package/dist/index.js +122 -9
- package/dist/lib/locks.d.ts +33 -0
- package/dist/lib/messages.d.ts +27 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/index-Bw0wMcXE.js +0 -186
- package/dashboard/dist/assets/index-CF_GDtNp.css +0 -1
- package/dashboard/dist/index.html +0 -13
- package/dashboard/dist/logo.jpg +0 -0
package/bin/mcp.js
CHANGED
|
@@ -6,39 +6,60 @@ var __defProp = Object.defineProperty;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
function __accessProp(key) {
|
|
10
|
+
return this[key];
|
|
11
|
+
}
|
|
12
|
+
var __toESMCache_node;
|
|
13
|
+
var __toESMCache_esm;
|
|
9
14
|
var __toESM = (mod, isNodeMode, target) => {
|
|
15
|
+
var canCache = mod != null && typeof mod === "object";
|
|
16
|
+
if (canCache) {
|
|
17
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
18
|
+
var cached = cache.get(mod);
|
|
19
|
+
if (cached)
|
|
20
|
+
return cached;
|
|
21
|
+
}
|
|
10
22
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
11
23
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
12
24
|
for (let key of __getOwnPropNames(mod))
|
|
13
25
|
if (!__hasOwnProp.call(to, key))
|
|
14
26
|
__defProp(to, key, {
|
|
15
|
-
get: (
|
|
27
|
+
get: __accessProp.bind(mod, key),
|
|
16
28
|
enumerable: true
|
|
17
29
|
});
|
|
30
|
+
if (canCache)
|
|
31
|
+
cache.set(mod, to);
|
|
18
32
|
return to;
|
|
19
33
|
};
|
|
20
|
-
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
21
34
|
var __toCommonJS = (from) => {
|
|
22
|
-
var entry = __moduleCache.get(from), desc;
|
|
35
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
23
36
|
if (entry)
|
|
24
37
|
return entry;
|
|
25
38
|
entry = __defProp({}, "__esModule", { value: true });
|
|
26
|
-
if (from && typeof from === "object" || typeof from === "function")
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
40
|
+
for (var key of __getOwnPropNames(from))
|
|
41
|
+
if (!__hasOwnProp.call(entry, key))
|
|
42
|
+
__defProp(entry, key, {
|
|
43
|
+
get: __accessProp.bind(from, key),
|
|
44
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
45
|
+
});
|
|
46
|
+
}
|
|
31
47
|
__moduleCache.set(from, entry);
|
|
32
48
|
return entry;
|
|
33
49
|
};
|
|
50
|
+
var __moduleCache;
|
|
34
51
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
52
|
+
var __returnValue = (v) => v;
|
|
53
|
+
function __exportSetter(name, newValue) {
|
|
54
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
55
|
+
}
|
|
35
56
|
var __export = (target, all) => {
|
|
36
57
|
for (var name in all)
|
|
37
58
|
__defProp(target, name, {
|
|
38
59
|
get: all[name],
|
|
39
60
|
enumerable: true,
|
|
40
61
|
configurable: true,
|
|
41
|
-
set: (
|
|
62
|
+
set: __exportSetter.bind(all, name)
|
|
42
63
|
});
|
|
43
64
|
};
|
|
44
65
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -6302,7 +6323,7 @@ var require_formats = __commonJS((exports) => {
|
|
|
6302
6323
|
}
|
|
6303
6324
|
var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
|
|
6304
6325
|
function getTime(strictTimeZone) {
|
|
6305
|
-
return function
|
|
6326
|
+
return function time3(str) {
|
|
6306
6327
|
const matches = TIME.exec(str);
|
|
6307
6328
|
if (!matches)
|
|
6308
6329
|
return false;
|
|
@@ -27310,6 +27331,62 @@ class ExperimentalServerTasks {
|
|
|
27310
27331
|
requestStream(request, resultSchema, options) {
|
|
27311
27332
|
return this._server.requestStream(request, resultSchema, options);
|
|
27312
27333
|
}
|
|
27334
|
+
createMessageStream(params, options) {
|
|
27335
|
+
const clientCapabilities = this._server.getClientCapabilities();
|
|
27336
|
+
if ((params.tools || params.toolChoice) && !clientCapabilities?.sampling?.tools) {
|
|
27337
|
+
throw new Error("Client does not support sampling tools capability.");
|
|
27338
|
+
}
|
|
27339
|
+
if (params.messages.length > 0) {
|
|
27340
|
+
const lastMessage = params.messages[params.messages.length - 1];
|
|
27341
|
+
const lastContent = Array.isArray(lastMessage.content) ? lastMessage.content : [lastMessage.content];
|
|
27342
|
+
const hasToolResults = lastContent.some((c) => c.type === "tool_result");
|
|
27343
|
+
const previousMessage = params.messages.length > 1 ? params.messages[params.messages.length - 2] : undefined;
|
|
27344
|
+
const previousContent = previousMessage ? Array.isArray(previousMessage.content) ? previousMessage.content : [previousMessage.content] : [];
|
|
27345
|
+
const hasPreviousToolUse = previousContent.some((c) => c.type === "tool_use");
|
|
27346
|
+
if (hasToolResults) {
|
|
27347
|
+
if (lastContent.some((c) => c.type !== "tool_result")) {
|
|
27348
|
+
throw new Error("The last message must contain only tool_result content if any is present");
|
|
27349
|
+
}
|
|
27350
|
+
if (!hasPreviousToolUse) {
|
|
27351
|
+
throw new Error("tool_result blocks are not matching any tool_use from the previous message");
|
|
27352
|
+
}
|
|
27353
|
+
}
|
|
27354
|
+
if (hasPreviousToolUse) {
|
|
27355
|
+
const toolUseIds = new Set(previousContent.filter((c) => c.type === "tool_use").map((c) => c.id));
|
|
27356
|
+
const toolResultIds = new Set(lastContent.filter((c) => c.type === "tool_result").map((c) => c.toolUseId));
|
|
27357
|
+
if (toolUseIds.size !== toolResultIds.size || ![...toolUseIds].every((id) => toolResultIds.has(id))) {
|
|
27358
|
+
throw new Error("ids of tool_result blocks and tool_use blocks from previous message do not match");
|
|
27359
|
+
}
|
|
27360
|
+
}
|
|
27361
|
+
}
|
|
27362
|
+
return this.requestStream({
|
|
27363
|
+
method: "sampling/createMessage",
|
|
27364
|
+
params
|
|
27365
|
+
}, CreateMessageResultSchema, options);
|
|
27366
|
+
}
|
|
27367
|
+
elicitInputStream(params, options) {
|
|
27368
|
+
const clientCapabilities = this._server.getClientCapabilities();
|
|
27369
|
+
const mode = params.mode ?? "form";
|
|
27370
|
+
switch (mode) {
|
|
27371
|
+
case "url": {
|
|
27372
|
+
if (!clientCapabilities?.elicitation?.url) {
|
|
27373
|
+
throw new Error("Client does not support url elicitation.");
|
|
27374
|
+
}
|
|
27375
|
+
break;
|
|
27376
|
+
}
|
|
27377
|
+
case "form": {
|
|
27378
|
+
if (!clientCapabilities?.elicitation?.form) {
|
|
27379
|
+
throw new Error("Client does not support form elicitation.");
|
|
27380
|
+
}
|
|
27381
|
+
break;
|
|
27382
|
+
}
|
|
27383
|
+
}
|
|
27384
|
+
const normalizedParams = mode === "form" && params.mode === undefined ? { ...params, mode: "form" } : params;
|
|
27385
|
+
return this.requestStream({
|
|
27386
|
+
method: "elicitation/create",
|
|
27387
|
+
params: normalizedParams
|
|
27388
|
+
}, ElicitResultSchema, options);
|
|
27389
|
+
}
|
|
27313
27390
|
async getTask(taskId, options) {
|
|
27314
27391
|
return this._server.getTask({ taskId }, options);
|
|
27315
27392
|
}
|
|
@@ -28823,12 +28900,64 @@ function getMessageById(id) {
|
|
|
28823
28900
|
const row = db2.prepare("SELECT * FROM messages WHERE id = ?").get(id);
|
|
28824
28901
|
return row ? parseMessage(row) : null;
|
|
28825
28902
|
}
|
|
28903
|
+
function markReadByIds(ids) {
|
|
28904
|
+
const db2 = getDb();
|
|
28905
|
+
if (ids.length === 0)
|
|
28906
|
+
return 0;
|
|
28907
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
28908
|
+
const stmt = db2.prepare(`UPDATE messages SET read_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE id IN (${placeholders}) AND read_at IS NULL`);
|
|
28909
|
+
const result = stmt.run(...ids);
|
|
28910
|
+
return result.changes;
|
|
28911
|
+
}
|
|
28826
28912
|
function markAllRead(agent) {
|
|
28827
28913
|
const db2 = getDb();
|
|
28828
28914
|
const stmt = db2.prepare(`UPDATE messages SET read_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE to_agent = ? AND read_at IS NULL`);
|
|
28829
28915
|
const result = stmt.run(agent);
|
|
28830
28916
|
return result.changes;
|
|
28831
28917
|
}
|
|
28918
|
+
function readDigest(opts = {}) {
|
|
28919
|
+
const db2 = getDb();
|
|
28920
|
+
const countConditions = ["read_at IS NULL"];
|
|
28921
|
+
const countParams = [];
|
|
28922
|
+
if (opts.space) {
|
|
28923
|
+
countConditions.push("space = ?");
|
|
28924
|
+
countParams.push(opts.space);
|
|
28925
|
+
}
|
|
28926
|
+
if (opts.session_id) {
|
|
28927
|
+
countConditions.push("session_id = ?");
|
|
28928
|
+
countParams.push(opts.session_id);
|
|
28929
|
+
}
|
|
28930
|
+
if (opts.to) {
|
|
28931
|
+
countConditions.push("to_agent = ?");
|
|
28932
|
+
countParams.push(opts.to);
|
|
28933
|
+
}
|
|
28934
|
+
if (opts.since) {
|
|
28935
|
+
countConditions.push("created_at > ?");
|
|
28936
|
+
countParams.push(opts.since);
|
|
28937
|
+
}
|
|
28938
|
+
if (opts.project_id) {
|
|
28939
|
+
countConditions.push("project_id = ?");
|
|
28940
|
+
countParams.push(opts.project_id);
|
|
28941
|
+
}
|
|
28942
|
+
const countWhere = `WHERE ${countConditions.join(" AND ")}`;
|
|
28943
|
+
const totalUnread = db2.prepare(`SELECT COUNT(*) as n FROM messages ${countWhere}`).get(...countParams).n;
|
|
28944
|
+
const messages = readMessages({ ...opts, unread_only: opts.unread_only ?? true });
|
|
28945
|
+
if (messages.length > 0) {
|
|
28946
|
+
markReadByIds(messages.map((m) => m.id));
|
|
28947
|
+
}
|
|
28948
|
+
const digest = messages.map((m) => ({
|
|
28949
|
+
id: m.id,
|
|
28950
|
+
from: m.from_agent,
|
|
28951
|
+
created_at: m.created_at,
|
|
28952
|
+
preview: m.content.slice(0, 100) + (m.content.length > 100 ? "\u2026" : ""),
|
|
28953
|
+
priority: m.priority,
|
|
28954
|
+
has_attachments: Array.isArray(m.attachments) && m.attachments.length > 0,
|
|
28955
|
+
space: m.space,
|
|
28956
|
+
to: m.to_agent,
|
|
28957
|
+
unread: !m.read_at
|
|
28958
|
+
}));
|
|
28959
|
+
return { messages: digest, total_unread: totalUnread, shown: digest.length };
|
|
28960
|
+
}
|
|
28832
28961
|
function escapeCsvField(value) {
|
|
28833
28962
|
if (value === null || value === undefined)
|
|
28834
28963
|
return "";
|
|
@@ -29994,10 +30123,12 @@ function getReactionSummary(messageId) {
|
|
|
29994
30123
|
// src/lib/locks.ts
|
|
29995
30124
|
init_db();
|
|
29996
30125
|
var DEFAULT_LOCK_EXPIRY_MS = 5 * 60 * 1000;
|
|
30126
|
+
var STALE_HEARTBEAT_SECONDS = 30 * 60;
|
|
29997
30127
|
function acquireLock(resourceType, resourceId, agentId, lockType = "advisory", expiryMs = DEFAULT_LOCK_EXPIRY_MS) {
|
|
29998
30128
|
const db2 = getDb();
|
|
29999
30129
|
return db2.transaction(() => {
|
|
30000
30130
|
cleanExpiredLocks();
|
|
30131
|
+
releaseStaleAgentLocks();
|
|
30001
30132
|
const existing = db2.prepare(`
|
|
30002
30133
|
SELECT * FROM resource_locks
|
|
30003
30134
|
WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
|
|
@@ -30024,6 +30155,55 @@ function acquireLock(resourceType, resourceId, agentId, lockType = "advisory", e
|
|
|
30024
30155
|
return { acquired: true, lock };
|
|
30025
30156
|
}).immediate();
|
|
30026
30157
|
}
|
|
30158
|
+
function bulkAcquireLock(resources, agentId) {
|
|
30159
|
+
const db2 = getDb();
|
|
30160
|
+
return db2.transaction(() => {
|
|
30161
|
+
cleanExpiredLocks();
|
|
30162
|
+
releaseStaleAgentLocks();
|
|
30163
|
+
const acquired = [];
|
|
30164
|
+
for (const { resource_type, resource_id, lock_type = "advisory", expiry_ms = DEFAULT_LOCK_EXPIRY_MS } of resources) {
|
|
30165
|
+
const existing = db2.prepare(`
|
|
30166
|
+
SELECT * FROM resource_locks
|
|
30167
|
+
WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
|
|
30168
|
+
`).get(resource_type, resource_id, lock_type);
|
|
30169
|
+
if (existing && existing.agent_id !== agentId) {
|
|
30170
|
+
throw { _bulkConflict: true, resource_type, resource_id, held_by: existing.agent_id };
|
|
30171
|
+
}
|
|
30172
|
+
const expiresAt = new Date(Date.now() + expiry_ms).toISOString().slice(0, -1);
|
|
30173
|
+
if (existing) {
|
|
30174
|
+
db2.prepare(`
|
|
30175
|
+
UPDATE resource_locks SET expires_at = ?, locked_at = strftime('%Y-%m-%dT%H:%M:%f', 'now')
|
|
30176
|
+
WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
|
|
30177
|
+
`).run(expiresAt, resource_type, resource_id, lock_type);
|
|
30178
|
+
} else {
|
|
30179
|
+
db2.prepare(`
|
|
30180
|
+
INSERT INTO resource_locks (resource_type, resource_id, agent_id, lock_type, locked_at, expires_at)
|
|
30181
|
+
VALUES (?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'), ?)
|
|
30182
|
+
`).run(resource_type, resource_id, agentId, lock_type, expiresAt);
|
|
30183
|
+
}
|
|
30184
|
+
const lock = db2.prepare(`
|
|
30185
|
+
SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
|
|
30186
|
+
`).get(resource_type, resource_id, lock_type);
|
|
30187
|
+
acquired.push(lock);
|
|
30188
|
+
}
|
|
30189
|
+
return { acquired: true, locks: acquired };
|
|
30190
|
+
}).immediate();
|
|
30191
|
+
}
|
|
30192
|
+
function tryBulkAcquireLock(resources, agentId) {
|
|
30193
|
+
try {
|
|
30194
|
+
return bulkAcquireLock(resources, agentId);
|
|
30195
|
+
} catch (err) {
|
|
30196
|
+
const e = err;
|
|
30197
|
+
if (e?._bulkConflict) {
|
|
30198
|
+
return {
|
|
30199
|
+
acquired: false,
|
|
30200
|
+
locks: [],
|
|
30201
|
+
blocked_by: { resource_type: e.resource_type, resource_id: e.resource_id, held_by: e.held_by }
|
|
30202
|
+
};
|
|
30203
|
+
}
|
|
30204
|
+
throw err;
|
|
30205
|
+
}
|
|
30206
|
+
}
|
|
30027
30207
|
function releaseLock(resourceType, resourceId, agentId) {
|
|
30028
30208
|
const db2 = getDb();
|
|
30029
30209
|
const result = db2.prepare(`
|
|
@@ -30035,6 +30215,7 @@ function releaseLock(resourceType, resourceId, agentId) {
|
|
|
30035
30215
|
function checkLock(resourceType, resourceId) {
|
|
30036
30216
|
const db2 = getDb();
|
|
30037
30217
|
cleanExpiredLocks();
|
|
30218
|
+
releaseStaleAgentLocks();
|
|
30038
30219
|
return db2.prepare(`
|
|
30039
30220
|
SELECT * FROM resource_locks
|
|
30040
30221
|
WHERE resource_type = ? AND resource_id = ?
|
|
@@ -30042,6 +30223,17 @@ function checkLock(resourceType, resourceId) {
|
|
|
30042
30223
|
LIMIT 1
|
|
30043
30224
|
`).get(resourceType, resourceId);
|
|
30044
30225
|
}
|
|
30226
|
+
function releaseStaleAgentLocks() {
|
|
30227
|
+
const db2 = getDb();
|
|
30228
|
+
const result = db2.prepare(`
|
|
30229
|
+
DELETE FROM resource_locks
|
|
30230
|
+
WHERE LOWER(agent_id) IN (
|
|
30231
|
+
SELECT LOWER(agent) FROM agent_presence
|
|
30232
|
+
WHERE last_seen_at < strftime('%Y-%m-%dT%H:%M:%f', 'now', '-${STALE_HEARTBEAT_SECONDS} seconds')
|
|
30233
|
+
)
|
|
30234
|
+
`).run();
|
|
30235
|
+
return result.changes;
|
|
30236
|
+
}
|
|
30045
30237
|
function cleanExpiredLocks() {
|
|
30046
30238
|
const db2 = getDb();
|
|
30047
30239
|
const result = db2.prepare(`
|
|
@@ -30052,6 +30244,7 @@ function cleanExpiredLocks() {
|
|
|
30052
30244
|
function listLocks(opts) {
|
|
30053
30245
|
const db2 = getDb();
|
|
30054
30246
|
cleanExpiredLocks();
|
|
30247
|
+
releaseStaleAgentLocks();
|
|
30055
30248
|
let query = "SELECT * FROM resource_locks WHERE 1=1";
|
|
30056
30249
|
const params = [];
|
|
30057
30250
|
if (opts?.resource_type) {
|
|
@@ -30065,6 +30258,31 @@ function listLocks(opts) {
|
|
|
30065
30258
|
query += " ORDER BY locked_at ASC";
|
|
30066
30259
|
return db2.prepare(query).all(...params);
|
|
30067
30260
|
}
|
|
30261
|
+
function listLocksEnriched(opts) {
|
|
30262
|
+
const locks = listLocks(opts);
|
|
30263
|
+
const db2 = getDb();
|
|
30264
|
+
const nowMs = Date.now();
|
|
30265
|
+
return locks.map((lock) => {
|
|
30266
|
+
const lockedMs = new Date(lock.locked_at + "Z").getTime();
|
|
30267
|
+
const expiresMs = new Date(lock.expires_at + "Z").getTime();
|
|
30268
|
+
const presenceRow = db2.prepare(`
|
|
30269
|
+
SELECT role, status, last_seen_at, project_id FROM agent_presence WHERE LOWER(agent) = LOWER(?)
|
|
30270
|
+
`).get(lock.agent_id);
|
|
30271
|
+
const agent = presenceRow ? {
|
|
30272
|
+
role: presenceRow.role ?? null,
|
|
30273
|
+
status: presenceRow.status ?? null,
|
|
30274
|
+
online: presenceRow.last_seen_at ? nowMs - new Date(presenceRow.last_seen_at + "Z").getTime() < 60000 : false,
|
|
30275
|
+
last_seen_at: presenceRow.last_seen_at ?? null,
|
|
30276
|
+
project_id: presenceRow.project_id ?? null
|
|
30277
|
+
} : null;
|
|
30278
|
+
return {
|
|
30279
|
+
...lock,
|
|
30280
|
+
locked_seconds_ago: Math.round((nowMs - lockedMs) / 1000),
|
|
30281
|
+
expires_in_seconds: Math.round((expiresMs - nowMs) / 1000),
|
|
30282
|
+
agent
|
|
30283
|
+
};
|
|
30284
|
+
});
|
|
30285
|
+
}
|
|
30068
30286
|
|
|
30069
30287
|
// src/lib/hot.ts
|
|
30070
30288
|
init_db();
|
|
@@ -30549,7 +30767,7 @@ function getGraphStats() {
|
|
|
30549
30767
|
// package.json
|
|
30550
30768
|
var package_default = {
|
|
30551
30769
|
name: "@hasna/conversations",
|
|
30552
|
-
version: "0.2.
|
|
30770
|
+
version: "0.2.2",
|
|
30553
30771
|
description: "Real-time CLI messaging for AI agents",
|
|
30554
30772
|
type: "module",
|
|
30555
30773
|
bin: {
|
|
@@ -30679,7 +30897,8 @@ server.registerTool("read_messages", {
|
|
|
30679
30897
|
project_id: exports_external.string().optional(),
|
|
30680
30898
|
since: exports_external.string().optional(),
|
|
30681
30899
|
limit: exports_external.coerce.number().optional(),
|
|
30682
|
-
unread_only: exports_external.coerce.boolean().optional()
|
|
30900
|
+
unread_only: exports_external.coerce.boolean().optional(),
|
|
30901
|
+
mark_read: exports_external.coerce.boolean().optional()
|
|
30683
30902
|
}
|
|
30684
30903
|
}, async (args) => {
|
|
30685
30904
|
const agent = resolveIdentity(args.from);
|
|
@@ -30687,6 +30906,9 @@ server.registerTool("read_messages", {
|
|
|
30687
30906
|
...args,
|
|
30688
30907
|
project_id: args.project_id ?? resolveProjectId(undefined, agent)
|
|
30689
30908
|
});
|
|
30909
|
+
if (args.mark_read !== false && messages.length > 0) {
|
|
30910
|
+
markReadByIds(messages.map((m) => m.id));
|
|
30911
|
+
}
|
|
30690
30912
|
return {
|
|
30691
30913
|
content: [{ type: "text", text: JSON.stringify(messages) }]
|
|
30692
30914
|
};
|
|
@@ -30882,15 +31104,36 @@ server.registerTool("read_space", {
|
|
|
30882
31104
|
inputSchema: {
|
|
30883
31105
|
space: exports_external.string(),
|
|
30884
31106
|
since: exports_external.string().optional(),
|
|
30885
|
-
limit: exports_external.coerce.number().optional()
|
|
31107
|
+
limit: exports_external.coerce.number().optional(),
|
|
31108
|
+
mark_read: exports_external.coerce.boolean().optional()
|
|
30886
31109
|
}
|
|
30887
31110
|
}, async (args) => {
|
|
30888
|
-
const { space, since, limit } = args;
|
|
31111
|
+
const { space, since, limit, mark_read } = args;
|
|
30889
31112
|
const messages = readMessages({ space, since, limit });
|
|
31113
|
+
if (mark_read !== false && messages.length > 0) {
|
|
31114
|
+
markReadByIds(messages.map((m) => m.id));
|
|
31115
|
+
}
|
|
30890
31116
|
return {
|
|
30891
31117
|
content: [{ type: "text", text: JSON.stringify(messages) }]
|
|
30892
31118
|
};
|
|
30893
31119
|
});
|
|
31120
|
+
server.registerTool("read_digest", {
|
|
31121
|
+
description: "Lightweight unread message digest \u2014 returns preview-only summaries, auto-marks as read. Use instead of read_messages on busy spaces to avoid token overflow.",
|
|
31122
|
+
inputSchema: {
|
|
31123
|
+
space: exports_external.string().optional(),
|
|
31124
|
+
session_id: exports_external.string().optional(),
|
|
31125
|
+
to: exports_external.string().optional(),
|
|
31126
|
+
since: exports_external.string().optional(),
|
|
31127
|
+
limit: exports_external.coerce.number().optional(),
|
|
31128
|
+
project_id: exports_external.string().optional()
|
|
31129
|
+
}
|
|
31130
|
+
}, async (args) => {
|
|
31131
|
+
const { space, session_id, to, since, limit, project_id } = args;
|
|
31132
|
+
const result = readDigest({ space, session_id, to, since, limit, project_id });
|
|
31133
|
+
return {
|
|
31134
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
31135
|
+
};
|
|
31136
|
+
});
|
|
30894
31137
|
server.registerTool("join_space", {
|
|
30895
31138
|
description: "Join a space as a member.",
|
|
30896
31139
|
inputSchema: {
|
|
@@ -31423,18 +31666,29 @@ server.registerTool("get_reaction_summary", {
|
|
|
31423
31666
|
return { content: [{ type: "text", text: JSON.stringify(summary) }] };
|
|
31424
31667
|
});
|
|
31425
31668
|
server.registerTool("acquire_lock", {
|
|
31426
|
-
description: "Acquire an advisory or exclusive lock on a resource. Returns conflict info if another agent holds the lock.",
|
|
31669
|
+
description: "Acquire an advisory or exclusive lock on a resource. Returns conflict info if another agent holds the lock. On conflict, auto-DMs the holding agent.",
|
|
31427
31670
|
inputSchema: {
|
|
31428
31671
|
resource_type: exports_external.string(),
|
|
31429
31672
|
resource_id: exports_external.string(),
|
|
31430
31673
|
lock_type: exports_external.enum(["advisory", "exclusive"]).optional(),
|
|
31431
31674
|
expiry_ms: exports_external.coerce.number().optional(),
|
|
31432
|
-
from: exports_external.string().optional()
|
|
31675
|
+
from: exports_external.string().optional(),
|
|
31676
|
+
auto_dm: exports_external.coerce.boolean().optional()
|
|
31433
31677
|
}
|
|
31434
31678
|
}, async (args) => {
|
|
31435
|
-
const { resource_type, resource_id, lock_type, expiry_ms, from: fromParam } = args;
|
|
31679
|
+
const { resource_type, resource_id, lock_type, expiry_ms, from: fromParam, auto_dm } = args;
|
|
31436
31680
|
const agent = resolveIdentity(fromParam);
|
|
31437
31681
|
const result = acquireLock(resource_type, resource_id, agent, lock_type ?? "advisory", expiry_ms);
|
|
31682
|
+
if (!result.acquired && result.held_by && auto_dm !== false) {
|
|
31683
|
+
try {
|
|
31684
|
+
sendMessage({
|
|
31685
|
+
from: agent,
|
|
31686
|
+
to: result.held_by,
|
|
31687
|
+
content: `Lock conflict: I (@${agent}) tried to acquire ${lock_type ?? "advisory"} lock on \`${resource_type}/${resource_id}\` but you hold it. If you no longer need it, release it with \`release_lock\`.`,
|
|
31688
|
+
priority: "high"
|
|
31689
|
+
});
|
|
31690
|
+
} catch {}
|
|
31691
|
+
}
|
|
31438
31692
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
31439
31693
|
});
|
|
31440
31694
|
server.registerTool("release_lock", {
|
|
@@ -31461,15 +31715,50 @@ server.registerTool("check_lock", {
|
|
|
31461
31715
|
return { content: [{ type: "text", text: JSON.stringify(lock ?? { locked: false }) }] };
|
|
31462
31716
|
});
|
|
31463
31717
|
server.registerTool("list_locks", {
|
|
31464
|
-
description: "List all active (non-expired) locks. Filter by resource_type or agent.",
|
|
31718
|
+
description: "List all active (non-expired) locks enriched with agent presence details (status, online, last_seen_at) and time context (locked_seconds_ago, expires_in_seconds). Filter by resource_type or agent.",
|
|
31465
31719
|
inputSchema: {
|
|
31466
31720
|
resource_type: exports_external.string().optional(),
|
|
31467
31721
|
agent_id: exports_external.string().optional()
|
|
31468
31722
|
}
|
|
31469
31723
|
}, async (args) => {
|
|
31470
|
-
const locks =
|
|
31724
|
+
const locks = listLocksEnriched({ resource_type: args.resource_type, agent_id: args.agent_id });
|
|
31471
31725
|
return { content: [{ type: "text", text: JSON.stringify(locks) }] };
|
|
31472
31726
|
});
|
|
31727
|
+
server.registerTool("bulk_acquire_lock", {
|
|
31728
|
+
description: "Atomically acquire multiple locks at once. All-or-nothing: if any lock is held by another agent, none are acquired. Returns blocked_by info on conflict.",
|
|
31729
|
+
inputSchema: {
|
|
31730
|
+
resources: exports_external.array(exports_external.object({
|
|
31731
|
+
resource_type: exports_external.string(),
|
|
31732
|
+
resource_id: exports_external.string(),
|
|
31733
|
+
lock_type: exports_external.enum(["advisory", "exclusive"]).optional(),
|
|
31734
|
+
expiry_ms: exports_external.coerce.number().optional()
|
|
31735
|
+
})),
|
|
31736
|
+
from: exports_external.string().optional(),
|
|
31737
|
+
auto_dm: exports_external.coerce.boolean().optional()
|
|
31738
|
+
}
|
|
31739
|
+
}, async (args) => {
|
|
31740
|
+
const agent = resolveIdentity(args.from);
|
|
31741
|
+
const result = tryBulkAcquireLock(args.resources, agent);
|
|
31742
|
+
if (!result.acquired && result.blocked_by && args.auto_dm !== false) {
|
|
31743
|
+
try {
|
|
31744
|
+
sendMessage({
|
|
31745
|
+
from: agent,
|
|
31746
|
+
to: result.blocked_by.held_by,
|
|
31747
|
+
content: `Bulk lock conflict: I (@${agent}) tried to atomically acquire ${args.resources.length} locks but you hold \`${result.blocked_by.resource_type}/${result.blocked_by.resource_id}\`. Release it when done.`,
|
|
31748
|
+
priority: "high"
|
|
31749
|
+
});
|
|
31750
|
+
} catch {}
|
|
31751
|
+
}
|
|
31752
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
31753
|
+
});
|
|
31754
|
+
server.registerTool("clean_expired_locks", {
|
|
31755
|
+
description: "Clean up expired locks and auto-release locks held by agents whose heartbeat has been stale for >30 minutes. Returns counts of removed locks.",
|
|
31756
|
+
inputSchema: {}
|
|
31757
|
+
}, async () => {
|
|
31758
|
+
const stale = releaseStaleAgentLocks();
|
|
31759
|
+
const expired = cleanExpiredLocks();
|
|
31760
|
+
return { content: [{ type: "text", text: JSON.stringify({ released_stale_agent: stale, released_expired: expired, total: stale + expired }) }] };
|
|
31761
|
+
});
|
|
31473
31762
|
server.registerTool("get_thread_replies", {
|
|
31474
31763
|
description: "Get all replies in a thread for a given parent message ID.",
|
|
31475
31764
|
inputSchema: {
|
|
@@ -31653,6 +31942,7 @@ server.registerTool("search_tools", {
|
|
|
31653
31942
|
const all = [
|
|
31654
31943
|
"send_message",
|
|
31655
31944
|
"read_messages",
|
|
31945
|
+
"read_digest",
|
|
31656
31946
|
"list_sessions",
|
|
31657
31947
|
"reply",
|
|
31658
31948
|
"mark_read",
|
|
@@ -31691,9 +31981,11 @@ server.registerTool("search_tools", {
|
|
|
31691
31981
|
"get_reactions",
|
|
31692
31982
|
"get_reaction_summary",
|
|
31693
31983
|
"acquire_lock",
|
|
31984
|
+
"bulk_acquire_lock",
|
|
31694
31985
|
"release_lock",
|
|
31695
31986
|
"check_lock",
|
|
31696
31987
|
"list_locks",
|
|
31988
|
+
"clean_expired_locks",
|
|
31697
31989
|
"get_thread_replies",
|
|
31698
31990
|
"set_focus",
|
|
31699
31991
|
"get_focus",
|
|
@@ -31719,7 +32011,8 @@ server.registerTool("describe_tools", {
|
|
|
31719
32011
|
}, async ({ names }) => {
|
|
31720
32012
|
const descriptions = {
|
|
31721
32013
|
send_message: "Send DM to agent. Required: to, content. Optional: from?, priority?(low|normal|high|urgent), blocking?",
|
|
31722
|
-
read_messages: "Read messages with filters. Optional: session_id?, from?, to?, space?, since?(ISO), limit?, unread_only?",
|
|
32014
|
+
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)",
|
|
32015
|
+
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?",
|
|
31723
32016
|
list_sessions: "List all DM sessions. Optional: agent?(filter by participant)",
|
|
31724
32017
|
reply: "Reply to a message in same session. Required: message_id, content. Optional: from?",
|
|
31725
32018
|
mark_read: "Mark messages as read. Optional: from?, ids?(array), all?(bool \u2014 mark all unread)",
|
|
@@ -31728,7 +32021,7 @@ server.registerTool("describe_tools", {
|
|
|
31728
32021
|
create_space: "Create space and auto-join. Required: name. Optional: from?, description?, parent_id?(max 3 levels), project_id?",
|
|
31729
32022
|
list_spaces: "List spaces with member/message counts. Optional: project_id?, parent_id?(use 'null' for top-level), include_archived?",
|
|
31730
32023
|
send_to_space: "Post message to space. Required: space, content. Optional: from?, priority?(low|normal|high|urgent), blocking?",
|
|
31731
|
-
read_space: "Read messages in a space. Required: space. Optional: since?(ISO), limit?",
|
|
32024
|
+
read_space: "Read messages in a space. Required: space. Optional: since?(ISO), limit?, mark_read?(default true \u2014 auto-marks returned messages as read)",
|
|
31732
32025
|
join_space: "Join a space. Required: space. Optional: from?",
|
|
31733
32026
|
leave_space: "Leave a space. Required: space. Optional: from?",
|
|
31734
32027
|
update_space: "Update space fields. Required: name. Optional: description?, parent_id?(use 'null' to remove), project_id?(use 'null' to remove)",
|
|
@@ -31757,10 +32050,12 @@ server.registerTool("describe_tools", {
|
|
|
31757
32050
|
remove_reaction: "Remove emoji reaction from a message. Required: message_id, emoji. Optional: from?",
|
|
31758
32051
|
get_reactions: "Get all reactions for a message. Required: message_id",
|
|
31759
32052
|
get_reaction_summary: "Get emoji counts + agent lists for a message. Required: message_id",
|
|
31760
|
-
acquire_lock: "Acquire advisory/exclusive lock on a resource. Required: resource_type, resource_id. Optional: lock_type?(advisory|exclusive), expiry_ms?, from?",
|
|
32053
|
+
acquire_lock: "Acquire advisory/exclusive lock on a resource. On conflict, auto-DMs the holding agent. Required: resource_type, resource_id. Optional: lock_type?(advisory|exclusive), expiry_ms?, from?, auto_dm?(default true)",
|
|
32054
|
+
bulk_acquire_lock: "Atomically acquire multiple locks (all-or-nothing). Required: resources[]{resource_type,resource_id,lock_type?,expiry_ms?}. Optional: from?, auto_dm?(default true). Returns blocked_by on conflict.",
|
|
31761
32055
|
release_lock: "Release lock held by agent. Required: resource_type, resource_id. Optional: from?",
|
|
31762
32056
|
check_lock: "Check if resource is locked and who holds it. Required: resource_type, resource_id",
|
|
31763
|
-
list_locks: "List active locks. Optional: resource_type?, agent_id?",
|
|
32057
|
+
list_locks: "List active locks enriched with agent presence + time context. Optional: resource_type?, agent_id?",
|
|
32058
|
+
clean_expired_locks: "Release expired locks + locks held by agents with stale heartbeat (>30 min). Returns {released_stale_agent, released_expired, total}",
|
|
31764
32059
|
get_thread_replies: "Get all replies in a thread. Required: message_id. Optional: limit?",
|
|
31765
32060
|
set_focus: "Set agent focus to a project. All read tools default to this scope. Required: project_id. Optional: from?",
|
|
31766
32061
|
get_focus: "Get current focus: session focus, DB project_id, effective project_id. Optional: from?",
|
package/dist/index.d.ts
CHANGED
|
@@ -21,8 +21,8 @@ export { addReaction, removeReaction, getReactions, getReactionSummary, } from "
|
|
|
21
21
|
export type { ReactionSummary } from "./lib/reactions.js";
|
|
22
22
|
export { fireWebhooks, } from "./lib/webhooks.js";
|
|
23
23
|
export { heartbeat, registerAgent, isAgentConflict, getPresence, listAgents, removePresence, renameAgent, } from "./lib/presence.js";
|
|
24
|
-
export { acquireLock, releaseLock, checkLock, cleanExpiredLocks, listLocks, } from "./lib/locks.js";
|
|
25
|
-
export type { ResourceLock } from "./lib/locks.js";
|
|
24
|
+
export { acquireLock, tryBulkAcquireLock, releaseLock, checkLock, cleanExpiredLocks, releaseStaleAgentLocks, listLocks, listLocksEnriched, } from "./lib/locks.js";
|
|
25
|
+
export type { ResourceLock, EnrichedLock, BulkLockRequest, BulkAcquireResult } from "./lib/locks.js";
|
|
26
26
|
export { computeHotness, listHotSessions, } from "./lib/hot.js";
|
|
27
27
|
export type { HotSession, HotSessionsOptions } from "./lib/hot.js";
|
|
28
28
|
export { extractTopics, getSpaceTopics, getSessionTopics, getTrendingTopics, } from "./lib/topics.js";
|