@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/index.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);
|
|
@@ -2382,12 +2403,64 @@ function getMessageById(id) {
|
|
|
2382
2403
|
const row = db2.prepare("SELECT * FROM messages WHERE id = ?").get(id);
|
|
2383
2404
|
return row ? parseMessage(row) : null;
|
|
2384
2405
|
}
|
|
2406
|
+
function markReadByIds(ids) {
|
|
2407
|
+
const db2 = getDb();
|
|
2408
|
+
if (ids.length === 0)
|
|
2409
|
+
return 0;
|
|
2410
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
2411
|
+
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`);
|
|
2412
|
+
const result = stmt.run(...ids);
|
|
2413
|
+
return result.changes;
|
|
2414
|
+
}
|
|
2385
2415
|
function markAllRead(agent) {
|
|
2386
2416
|
const db2 = getDb();
|
|
2387
2417
|
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`);
|
|
2388
2418
|
const result = stmt.run(agent);
|
|
2389
2419
|
return result.changes;
|
|
2390
2420
|
}
|
|
2421
|
+
function readDigest(opts = {}) {
|
|
2422
|
+
const db2 = getDb();
|
|
2423
|
+
const countConditions = ["read_at IS NULL"];
|
|
2424
|
+
const countParams = [];
|
|
2425
|
+
if (opts.space) {
|
|
2426
|
+
countConditions.push("space = ?");
|
|
2427
|
+
countParams.push(opts.space);
|
|
2428
|
+
}
|
|
2429
|
+
if (opts.session_id) {
|
|
2430
|
+
countConditions.push("session_id = ?");
|
|
2431
|
+
countParams.push(opts.session_id);
|
|
2432
|
+
}
|
|
2433
|
+
if (opts.to) {
|
|
2434
|
+
countConditions.push("to_agent = ?");
|
|
2435
|
+
countParams.push(opts.to);
|
|
2436
|
+
}
|
|
2437
|
+
if (opts.since) {
|
|
2438
|
+
countConditions.push("created_at > ?");
|
|
2439
|
+
countParams.push(opts.since);
|
|
2440
|
+
}
|
|
2441
|
+
if (opts.project_id) {
|
|
2442
|
+
countConditions.push("project_id = ?");
|
|
2443
|
+
countParams.push(opts.project_id);
|
|
2444
|
+
}
|
|
2445
|
+
const countWhere = `WHERE ${countConditions.join(" AND ")}`;
|
|
2446
|
+
const totalUnread = db2.prepare(`SELECT COUNT(*) as n FROM messages ${countWhere}`).get(...countParams).n;
|
|
2447
|
+
const messages = readMessages({ ...opts, unread_only: opts.unread_only ?? true });
|
|
2448
|
+
if (messages.length > 0) {
|
|
2449
|
+
markReadByIds(messages.map((m) => m.id));
|
|
2450
|
+
}
|
|
2451
|
+
const digest = messages.map((m) => ({
|
|
2452
|
+
id: m.id,
|
|
2453
|
+
from: m.from_agent,
|
|
2454
|
+
created_at: m.created_at,
|
|
2455
|
+
preview: m.content.slice(0, 100) + (m.content.length > 100 ? "\u2026" : ""),
|
|
2456
|
+
priority: m.priority,
|
|
2457
|
+
has_attachments: Array.isArray(m.attachments) && m.attachments.length > 0,
|
|
2458
|
+
space: m.space,
|
|
2459
|
+
to: m.to_agent,
|
|
2460
|
+
unread: !m.read_at
|
|
2461
|
+
}));
|
|
2462
|
+
return { messages: digest, total_unread: totalUnread, shown: digest.length };
|
|
2463
|
+
}
|
|
2391
2464
|
function escapeCsvField(value) {
|
|
2392
2465
|
if (value === null || value === undefined)
|
|
2393
2466
|
return "";
|
|
@@ -3431,6 +3504,9 @@ function isActiveSession(lastSeenAt) {
|
|
|
3431
3504
|
const nowMs = Date.now();
|
|
3432
3505
|
return nowMs - lastSeenMs < CONFLICT_THRESHOLD_SECONDS * 1000;
|
|
3433
3506
|
}
|
|
3507
|
+
function isAgentConflict(result) {
|
|
3508
|
+
return result.conflict === true;
|
|
3509
|
+
}
|
|
3434
3510
|
function registerAgent(name, sessionId, role, projectId) {
|
|
3435
3511
|
const db2 = getDb();
|
|
3436
3512
|
const normalizedName = name.trim().toLowerCase();
|
|
@@ -4207,7 +4283,7 @@ var init_poll = __esm(() => {
|
|
|
4207
4283
|
var require_package = __commonJS((exports, module) => {
|
|
4208
4284
|
module.exports = {
|
|
4209
4285
|
name: "@hasna/conversations",
|
|
4210
|
-
version: "0.2.
|
|
4286
|
+
version: "0.2.2",
|
|
4211
4287
|
description: "Real-time CLI messaging for AI agents",
|
|
4212
4288
|
type: "module",
|
|
4213
4289
|
bin: {
|
|
@@ -31575,7 +31651,7 @@ var require_formats = __commonJS((exports) => {
|
|
|
31575
31651
|
}
|
|
31576
31652
|
var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
|
|
31577
31653
|
function getTime(strictTimeZone) {
|
|
31578
|
-
return function
|
|
31654
|
+
return function time3(str) {
|
|
31579
31655
|
const matches = TIME.exec(str);
|
|
31580
31656
|
if (!matches)
|
|
31581
31657
|
return false;
|
|
@@ -31839,6 +31915,62 @@ class ExperimentalServerTasks {
|
|
|
31839
31915
|
requestStream(request, resultSchema, options) {
|
|
31840
31916
|
return this._server.requestStream(request, resultSchema, options);
|
|
31841
31917
|
}
|
|
31918
|
+
createMessageStream(params, options) {
|
|
31919
|
+
const clientCapabilities = this._server.getClientCapabilities();
|
|
31920
|
+
if ((params.tools || params.toolChoice) && !clientCapabilities?.sampling?.tools) {
|
|
31921
|
+
throw new Error("Client does not support sampling tools capability.");
|
|
31922
|
+
}
|
|
31923
|
+
if (params.messages.length > 0) {
|
|
31924
|
+
const lastMessage = params.messages[params.messages.length - 1];
|
|
31925
|
+
const lastContent = Array.isArray(lastMessage.content) ? lastMessage.content : [lastMessage.content];
|
|
31926
|
+
const hasToolResults = lastContent.some((c) => c.type === "tool_result");
|
|
31927
|
+
const previousMessage = params.messages.length > 1 ? params.messages[params.messages.length - 2] : undefined;
|
|
31928
|
+
const previousContent = previousMessage ? Array.isArray(previousMessage.content) ? previousMessage.content : [previousMessage.content] : [];
|
|
31929
|
+
const hasPreviousToolUse = previousContent.some((c) => c.type === "tool_use");
|
|
31930
|
+
if (hasToolResults) {
|
|
31931
|
+
if (lastContent.some((c) => c.type !== "tool_result")) {
|
|
31932
|
+
throw new Error("The last message must contain only tool_result content if any is present");
|
|
31933
|
+
}
|
|
31934
|
+
if (!hasPreviousToolUse) {
|
|
31935
|
+
throw new Error("tool_result blocks are not matching any tool_use from the previous message");
|
|
31936
|
+
}
|
|
31937
|
+
}
|
|
31938
|
+
if (hasPreviousToolUse) {
|
|
31939
|
+
const toolUseIds = new Set(previousContent.filter((c) => c.type === "tool_use").map((c) => c.id));
|
|
31940
|
+
const toolResultIds = new Set(lastContent.filter((c) => c.type === "tool_result").map((c) => c.toolUseId));
|
|
31941
|
+
if (toolUseIds.size !== toolResultIds.size || ![...toolUseIds].every((id) => toolResultIds.has(id))) {
|
|
31942
|
+
throw new Error("ids of tool_result blocks and tool_use blocks from previous message do not match");
|
|
31943
|
+
}
|
|
31944
|
+
}
|
|
31945
|
+
}
|
|
31946
|
+
return this.requestStream({
|
|
31947
|
+
method: "sampling/createMessage",
|
|
31948
|
+
params
|
|
31949
|
+
}, CreateMessageResultSchema, options);
|
|
31950
|
+
}
|
|
31951
|
+
elicitInputStream(params, options) {
|
|
31952
|
+
const clientCapabilities = this._server.getClientCapabilities();
|
|
31953
|
+
const mode = params.mode ?? "form";
|
|
31954
|
+
switch (mode) {
|
|
31955
|
+
case "url": {
|
|
31956
|
+
if (!clientCapabilities?.elicitation?.url) {
|
|
31957
|
+
throw new Error("Client does not support url elicitation.");
|
|
31958
|
+
}
|
|
31959
|
+
break;
|
|
31960
|
+
}
|
|
31961
|
+
case "form": {
|
|
31962
|
+
if (!clientCapabilities?.elicitation?.form) {
|
|
31963
|
+
throw new Error("Client does not support form elicitation.");
|
|
31964
|
+
}
|
|
31965
|
+
break;
|
|
31966
|
+
}
|
|
31967
|
+
}
|
|
31968
|
+
const normalizedParams = mode === "form" && params.mode === undefined ? { ...params, mode: "form" } : params;
|
|
31969
|
+
return this.requestStream({
|
|
31970
|
+
method: "elicitation/create",
|
|
31971
|
+
params: normalizedParams
|
|
31972
|
+
}, ElicitResultSchema, options);
|
|
31973
|
+
}
|
|
31842
31974
|
async getTask(taskId, options) {
|
|
31843
31975
|
return this._server.getTask({ taskId }, options);
|
|
31844
31976
|
}
|
|
@@ -31852,6 +31984,9 @@ class ExperimentalServerTasks {
|
|
|
31852
31984
|
return this._server.cancelTask({ taskId }, options);
|
|
31853
31985
|
}
|
|
31854
31986
|
}
|
|
31987
|
+
var init_server = __esm(() => {
|
|
31988
|
+
init_types2();
|
|
31989
|
+
});
|
|
31855
31990
|
|
|
31856
31991
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
|
|
31857
31992
|
function assertToolsCallTaskCapability(requests, method, entityName) {
|
|
@@ -31890,11 +32025,12 @@ function assertClientRequestTaskCapability(requests, method, entityName) {
|
|
|
31890
32025
|
|
|
31891
32026
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
|
|
31892
32027
|
var Server;
|
|
31893
|
-
var
|
|
32028
|
+
var init_server2 = __esm(() => {
|
|
31894
32029
|
init_protocol();
|
|
31895
32030
|
init_types2();
|
|
31896
32031
|
init_ajv_provider();
|
|
31897
32032
|
init_zod_compat();
|
|
32033
|
+
init_server();
|
|
31898
32034
|
Server = class Server extends Protocol {
|
|
31899
32035
|
constructor(_serverInfo, options) {
|
|
31900
32036
|
super(options);
|
|
@@ -33036,7 +33172,7 @@ function createCompletionResult(suggestions) {
|
|
|
33036
33172
|
}
|
|
33037
33173
|
var EMPTY_OBJECT_JSON_SCHEMA, EMPTY_COMPLETION_RESULT;
|
|
33038
33174
|
var init_mcp = __esm(() => {
|
|
33039
|
-
|
|
33175
|
+
init_server2();
|
|
33040
33176
|
init_zod_compat();
|
|
33041
33177
|
init_zod_json_schema_compat();
|
|
33042
33178
|
init_types2();
|
|
@@ -33156,6 +33292,7 @@ function acquireLock(resourceType, resourceId, agentId, lockType = "advisory", e
|
|
|
33156
33292
|
const db2 = getDb();
|
|
33157
33293
|
return db2.transaction(() => {
|
|
33158
33294
|
cleanExpiredLocks();
|
|
33295
|
+
releaseStaleAgentLocks();
|
|
33159
33296
|
const existing = db2.prepare(`
|
|
33160
33297
|
SELECT * FROM resource_locks
|
|
33161
33298
|
WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
|
|
@@ -33182,6 +33319,55 @@ function acquireLock(resourceType, resourceId, agentId, lockType = "advisory", e
|
|
|
33182
33319
|
return { acquired: true, lock };
|
|
33183
33320
|
}).immediate();
|
|
33184
33321
|
}
|
|
33322
|
+
function bulkAcquireLock(resources, agentId) {
|
|
33323
|
+
const db2 = getDb();
|
|
33324
|
+
return db2.transaction(() => {
|
|
33325
|
+
cleanExpiredLocks();
|
|
33326
|
+
releaseStaleAgentLocks();
|
|
33327
|
+
const acquired = [];
|
|
33328
|
+
for (const { resource_type, resource_id, lock_type = "advisory", expiry_ms = DEFAULT_LOCK_EXPIRY_MS } of resources) {
|
|
33329
|
+
const existing = db2.prepare(`
|
|
33330
|
+
SELECT * FROM resource_locks
|
|
33331
|
+
WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
|
|
33332
|
+
`).get(resource_type, resource_id, lock_type);
|
|
33333
|
+
if (existing && existing.agent_id !== agentId) {
|
|
33334
|
+
throw { _bulkConflict: true, resource_type, resource_id, held_by: existing.agent_id };
|
|
33335
|
+
}
|
|
33336
|
+
const expiresAt = new Date(Date.now() + expiry_ms).toISOString().slice(0, -1);
|
|
33337
|
+
if (existing) {
|
|
33338
|
+
db2.prepare(`
|
|
33339
|
+
UPDATE resource_locks SET expires_at = ?, locked_at = strftime('%Y-%m-%dT%H:%M:%f', 'now')
|
|
33340
|
+
WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
|
|
33341
|
+
`).run(expiresAt, resource_type, resource_id, lock_type);
|
|
33342
|
+
} else {
|
|
33343
|
+
db2.prepare(`
|
|
33344
|
+
INSERT INTO resource_locks (resource_type, resource_id, agent_id, lock_type, locked_at, expires_at)
|
|
33345
|
+
VALUES (?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'), ?)
|
|
33346
|
+
`).run(resource_type, resource_id, agentId, lock_type, expiresAt);
|
|
33347
|
+
}
|
|
33348
|
+
const lock = db2.prepare(`
|
|
33349
|
+
SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
|
|
33350
|
+
`).get(resource_type, resource_id, lock_type);
|
|
33351
|
+
acquired.push(lock);
|
|
33352
|
+
}
|
|
33353
|
+
return { acquired: true, locks: acquired };
|
|
33354
|
+
}).immediate();
|
|
33355
|
+
}
|
|
33356
|
+
function tryBulkAcquireLock(resources, agentId) {
|
|
33357
|
+
try {
|
|
33358
|
+
return bulkAcquireLock(resources, agentId);
|
|
33359
|
+
} catch (err) {
|
|
33360
|
+
const e = err;
|
|
33361
|
+
if (e?._bulkConflict) {
|
|
33362
|
+
return {
|
|
33363
|
+
acquired: false,
|
|
33364
|
+
locks: [],
|
|
33365
|
+
blocked_by: { resource_type: e.resource_type, resource_id: e.resource_id, held_by: e.held_by }
|
|
33366
|
+
};
|
|
33367
|
+
}
|
|
33368
|
+
throw err;
|
|
33369
|
+
}
|
|
33370
|
+
}
|
|
33185
33371
|
function releaseLock(resourceType, resourceId, agentId) {
|
|
33186
33372
|
const db2 = getDb();
|
|
33187
33373
|
const result = db2.prepare(`
|
|
@@ -33193,6 +33379,7 @@ function releaseLock(resourceType, resourceId, agentId) {
|
|
|
33193
33379
|
function checkLock(resourceType, resourceId) {
|
|
33194
33380
|
const db2 = getDb();
|
|
33195
33381
|
cleanExpiredLocks();
|
|
33382
|
+
releaseStaleAgentLocks();
|
|
33196
33383
|
return db2.prepare(`
|
|
33197
33384
|
SELECT * FROM resource_locks
|
|
33198
33385
|
WHERE resource_type = ? AND resource_id = ?
|
|
@@ -33200,6 +33387,17 @@ function checkLock(resourceType, resourceId) {
|
|
|
33200
33387
|
LIMIT 1
|
|
33201
33388
|
`).get(resourceType, resourceId);
|
|
33202
33389
|
}
|
|
33390
|
+
function releaseStaleAgentLocks() {
|
|
33391
|
+
const db2 = getDb();
|
|
33392
|
+
const result = db2.prepare(`
|
|
33393
|
+
DELETE FROM resource_locks
|
|
33394
|
+
WHERE LOWER(agent_id) IN (
|
|
33395
|
+
SELECT LOWER(agent) FROM agent_presence
|
|
33396
|
+
WHERE last_seen_at < strftime('%Y-%m-%dT%H:%M:%f', 'now', '-${STALE_HEARTBEAT_SECONDS} seconds')
|
|
33397
|
+
)
|
|
33398
|
+
`).run();
|
|
33399
|
+
return result.changes;
|
|
33400
|
+
}
|
|
33203
33401
|
function cleanExpiredLocks() {
|
|
33204
33402
|
const db2 = getDb();
|
|
33205
33403
|
const result = db2.prepare(`
|
|
@@ -33210,6 +33408,7 @@ function cleanExpiredLocks() {
|
|
|
33210
33408
|
function listLocks(opts) {
|
|
33211
33409
|
const db2 = getDb();
|
|
33212
33410
|
cleanExpiredLocks();
|
|
33411
|
+
releaseStaleAgentLocks();
|
|
33213
33412
|
let query = "SELECT * FROM resource_locks WHERE 1=1";
|
|
33214
33413
|
const params = [];
|
|
33215
33414
|
if (opts?.resource_type) {
|
|
@@ -33223,10 +33422,36 @@ function listLocks(opts) {
|
|
|
33223
33422
|
query += " ORDER BY locked_at ASC";
|
|
33224
33423
|
return db2.prepare(query).all(...params);
|
|
33225
33424
|
}
|
|
33226
|
-
|
|
33425
|
+
function listLocksEnriched(opts) {
|
|
33426
|
+
const locks = listLocks(opts);
|
|
33427
|
+
const db2 = getDb();
|
|
33428
|
+
const nowMs = Date.now();
|
|
33429
|
+
return locks.map((lock) => {
|
|
33430
|
+
const lockedMs = new Date(lock.locked_at + "Z").getTime();
|
|
33431
|
+
const expiresMs = new Date(lock.expires_at + "Z").getTime();
|
|
33432
|
+
const presenceRow = db2.prepare(`
|
|
33433
|
+
SELECT role, status, last_seen_at, project_id FROM agent_presence WHERE LOWER(agent) = LOWER(?)
|
|
33434
|
+
`).get(lock.agent_id);
|
|
33435
|
+
const agent = presenceRow ? {
|
|
33436
|
+
role: presenceRow.role ?? null,
|
|
33437
|
+
status: presenceRow.status ?? null,
|
|
33438
|
+
online: presenceRow.last_seen_at ? nowMs - new Date(presenceRow.last_seen_at + "Z").getTime() < 60000 : false,
|
|
33439
|
+
last_seen_at: presenceRow.last_seen_at ?? null,
|
|
33440
|
+
project_id: presenceRow.project_id ?? null
|
|
33441
|
+
} : null;
|
|
33442
|
+
return {
|
|
33443
|
+
...lock,
|
|
33444
|
+
locked_seconds_ago: Math.round((nowMs - lockedMs) / 1000),
|
|
33445
|
+
expires_in_seconds: Math.round((expiresMs - nowMs) / 1000),
|
|
33446
|
+
agent
|
|
33447
|
+
};
|
|
33448
|
+
});
|
|
33449
|
+
}
|
|
33450
|
+
var DEFAULT_LOCK_EXPIRY_MS, STALE_HEARTBEAT_SECONDS;
|
|
33227
33451
|
var init_locks = __esm(() => {
|
|
33228
33452
|
init_db();
|
|
33229
33453
|
DEFAULT_LOCK_EXPIRY_MS = 5 * 60 * 1000;
|
|
33454
|
+
STALE_HEARTBEAT_SECONDS = 30 * 60;
|
|
33230
33455
|
});
|
|
33231
33456
|
|
|
33232
33457
|
// src/mcp/index.ts
|
|
@@ -33309,7 +33534,8 @@ var init_mcp2 = __esm(() => {
|
|
|
33309
33534
|
project_id: exports_external.string().optional(),
|
|
33310
33535
|
since: exports_external.string().optional(),
|
|
33311
33536
|
limit: exports_external.coerce.number().optional(),
|
|
33312
|
-
unread_only: exports_external.coerce.boolean().optional()
|
|
33537
|
+
unread_only: exports_external.coerce.boolean().optional(),
|
|
33538
|
+
mark_read: exports_external.coerce.boolean().optional()
|
|
33313
33539
|
}
|
|
33314
33540
|
}, async (args) => {
|
|
33315
33541
|
const agent = resolveIdentity(args.from);
|
|
@@ -33317,6 +33543,9 @@ var init_mcp2 = __esm(() => {
|
|
|
33317
33543
|
...args,
|
|
33318
33544
|
project_id: args.project_id ?? resolveProjectId(undefined, agent)
|
|
33319
33545
|
});
|
|
33546
|
+
if (args.mark_read !== false && messages.length > 0) {
|
|
33547
|
+
markReadByIds(messages.map((m) => m.id));
|
|
33548
|
+
}
|
|
33320
33549
|
return {
|
|
33321
33550
|
content: [{ type: "text", text: JSON.stringify(messages) }]
|
|
33322
33551
|
};
|
|
@@ -33512,15 +33741,36 @@ var init_mcp2 = __esm(() => {
|
|
|
33512
33741
|
inputSchema: {
|
|
33513
33742
|
space: exports_external.string(),
|
|
33514
33743
|
since: exports_external.string().optional(),
|
|
33515
|
-
limit: exports_external.coerce.number().optional()
|
|
33744
|
+
limit: exports_external.coerce.number().optional(),
|
|
33745
|
+
mark_read: exports_external.coerce.boolean().optional()
|
|
33516
33746
|
}
|
|
33517
33747
|
}, async (args) => {
|
|
33518
|
-
const { space, since, limit } = args;
|
|
33748
|
+
const { space, since, limit, mark_read } = args;
|
|
33519
33749
|
const messages = readMessages({ space, since, limit });
|
|
33750
|
+
if (mark_read !== false && messages.length > 0) {
|
|
33751
|
+
markReadByIds(messages.map((m) => m.id));
|
|
33752
|
+
}
|
|
33520
33753
|
return {
|
|
33521
33754
|
content: [{ type: "text", text: JSON.stringify(messages) }]
|
|
33522
33755
|
};
|
|
33523
33756
|
});
|
|
33757
|
+
server.registerTool("read_digest", {
|
|
33758
|
+
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.",
|
|
33759
|
+
inputSchema: {
|
|
33760
|
+
space: exports_external.string().optional(),
|
|
33761
|
+
session_id: exports_external.string().optional(),
|
|
33762
|
+
to: exports_external.string().optional(),
|
|
33763
|
+
since: exports_external.string().optional(),
|
|
33764
|
+
limit: exports_external.coerce.number().optional(),
|
|
33765
|
+
project_id: exports_external.string().optional()
|
|
33766
|
+
}
|
|
33767
|
+
}, async (args) => {
|
|
33768
|
+
const { space, session_id, to, since, limit, project_id } = args;
|
|
33769
|
+
const result = readDigest({ space, session_id, to, since, limit, project_id });
|
|
33770
|
+
return {
|
|
33771
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
33772
|
+
};
|
|
33773
|
+
});
|
|
33524
33774
|
server.registerTool("join_space", {
|
|
33525
33775
|
description: "Join a space as a member.",
|
|
33526
33776
|
inputSchema: {
|
|
@@ -34053,18 +34303,29 @@ var init_mcp2 = __esm(() => {
|
|
|
34053
34303
|
return { content: [{ type: "text", text: JSON.stringify(summary) }] };
|
|
34054
34304
|
});
|
|
34055
34305
|
server.registerTool("acquire_lock", {
|
|
34056
|
-
description: "Acquire an advisory or exclusive lock on a resource. Returns conflict info if another agent holds the lock.",
|
|
34306
|
+
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.",
|
|
34057
34307
|
inputSchema: {
|
|
34058
34308
|
resource_type: exports_external.string(),
|
|
34059
34309
|
resource_id: exports_external.string(),
|
|
34060
34310
|
lock_type: exports_external.enum(["advisory", "exclusive"]).optional(),
|
|
34061
34311
|
expiry_ms: exports_external.coerce.number().optional(),
|
|
34062
|
-
from: exports_external.string().optional()
|
|
34312
|
+
from: exports_external.string().optional(),
|
|
34313
|
+
auto_dm: exports_external.coerce.boolean().optional()
|
|
34063
34314
|
}
|
|
34064
34315
|
}, async (args) => {
|
|
34065
|
-
const { resource_type, resource_id, lock_type, expiry_ms, from: fromParam } = args;
|
|
34316
|
+
const { resource_type, resource_id, lock_type, expiry_ms, from: fromParam, auto_dm } = args;
|
|
34066
34317
|
const agent = resolveIdentity(fromParam);
|
|
34067
34318
|
const result = acquireLock(resource_type, resource_id, agent, lock_type ?? "advisory", expiry_ms);
|
|
34319
|
+
if (!result.acquired && result.held_by && auto_dm !== false) {
|
|
34320
|
+
try {
|
|
34321
|
+
sendMessage({
|
|
34322
|
+
from: agent,
|
|
34323
|
+
to: result.held_by,
|
|
34324
|
+
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\`.`,
|
|
34325
|
+
priority: "high"
|
|
34326
|
+
});
|
|
34327
|
+
} catch {}
|
|
34328
|
+
}
|
|
34068
34329
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
34069
34330
|
});
|
|
34070
34331
|
server.registerTool("release_lock", {
|
|
@@ -34091,15 +34352,50 @@ var init_mcp2 = __esm(() => {
|
|
|
34091
34352
|
return { content: [{ type: "text", text: JSON.stringify(lock ?? { locked: false }) }] };
|
|
34092
34353
|
});
|
|
34093
34354
|
server.registerTool("list_locks", {
|
|
34094
|
-
description: "List all active (non-expired) locks. Filter by resource_type or agent.",
|
|
34355
|
+
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.",
|
|
34095
34356
|
inputSchema: {
|
|
34096
34357
|
resource_type: exports_external.string().optional(),
|
|
34097
34358
|
agent_id: exports_external.string().optional()
|
|
34098
34359
|
}
|
|
34099
34360
|
}, async (args) => {
|
|
34100
|
-
const locks =
|
|
34361
|
+
const locks = listLocksEnriched({ resource_type: args.resource_type, agent_id: args.agent_id });
|
|
34101
34362
|
return { content: [{ type: "text", text: JSON.stringify(locks) }] };
|
|
34102
34363
|
});
|
|
34364
|
+
server.registerTool("bulk_acquire_lock", {
|
|
34365
|
+
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.",
|
|
34366
|
+
inputSchema: {
|
|
34367
|
+
resources: exports_external.array(exports_external.object({
|
|
34368
|
+
resource_type: exports_external.string(),
|
|
34369
|
+
resource_id: exports_external.string(),
|
|
34370
|
+
lock_type: exports_external.enum(["advisory", "exclusive"]).optional(),
|
|
34371
|
+
expiry_ms: exports_external.coerce.number().optional()
|
|
34372
|
+
})),
|
|
34373
|
+
from: exports_external.string().optional(),
|
|
34374
|
+
auto_dm: exports_external.coerce.boolean().optional()
|
|
34375
|
+
}
|
|
34376
|
+
}, async (args) => {
|
|
34377
|
+
const agent = resolveIdentity(args.from);
|
|
34378
|
+
const result = tryBulkAcquireLock(args.resources, agent);
|
|
34379
|
+
if (!result.acquired && result.blocked_by && args.auto_dm !== false) {
|
|
34380
|
+
try {
|
|
34381
|
+
sendMessage({
|
|
34382
|
+
from: agent,
|
|
34383
|
+
to: result.blocked_by.held_by,
|
|
34384
|
+
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.`,
|
|
34385
|
+
priority: "high"
|
|
34386
|
+
});
|
|
34387
|
+
} catch {}
|
|
34388
|
+
}
|
|
34389
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
34390
|
+
});
|
|
34391
|
+
server.registerTool("clean_expired_locks", {
|
|
34392
|
+
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.",
|
|
34393
|
+
inputSchema: {}
|
|
34394
|
+
}, async () => {
|
|
34395
|
+
const stale = releaseStaleAgentLocks();
|
|
34396
|
+
const expired = cleanExpiredLocks();
|
|
34397
|
+
return { content: [{ type: "text", text: JSON.stringify({ released_stale_agent: stale, released_expired: expired, total: stale + expired }) }] };
|
|
34398
|
+
});
|
|
34103
34399
|
server.registerTool("get_thread_replies", {
|
|
34104
34400
|
description: "Get all replies in a thread for a given parent message ID.",
|
|
34105
34401
|
inputSchema: {
|
|
@@ -34283,6 +34579,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34283
34579
|
const all = [
|
|
34284
34580
|
"send_message",
|
|
34285
34581
|
"read_messages",
|
|
34582
|
+
"read_digest",
|
|
34286
34583
|
"list_sessions",
|
|
34287
34584
|
"reply",
|
|
34288
34585
|
"mark_read",
|
|
@@ -34321,9 +34618,11 @@ var init_mcp2 = __esm(() => {
|
|
|
34321
34618
|
"get_reactions",
|
|
34322
34619
|
"get_reaction_summary",
|
|
34323
34620
|
"acquire_lock",
|
|
34621
|
+
"bulk_acquire_lock",
|
|
34324
34622
|
"release_lock",
|
|
34325
34623
|
"check_lock",
|
|
34326
34624
|
"list_locks",
|
|
34625
|
+
"clean_expired_locks",
|
|
34327
34626
|
"get_thread_replies",
|
|
34328
34627
|
"set_focus",
|
|
34329
34628
|
"get_focus",
|
|
@@ -34349,7 +34648,8 @@ var init_mcp2 = __esm(() => {
|
|
|
34349
34648
|
}, async ({ names }) => {
|
|
34350
34649
|
const descriptions = {
|
|
34351
34650
|
send_message: "Send DM to agent. Required: to, content. Optional: from?, priority?(low|normal|high|urgent), blocking?",
|
|
34352
|
-
read_messages: "Read messages with filters. Optional: session_id?, from?, to?, space?, since?(ISO), limit?, unread_only?",
|
|
34651
|
+
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)",
|
|
34652
|
+
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?",
|
|
34353
34653
|
list_sessions: "List all DM sessions. Optional: agent?(filter by participant)",
|
|
34354
34654
|
reply: "Reply to a message in same session. Required: message_id, content. Optional: from?",
|
|
34355
34655
|
mark_read: "Mark messages as read. Optional: from?, ids?(array), all?(bool \u2014 mark all unread)",
|
|
@@ -34358,7 +34658,7 @@ var init_mcp2 = __esm(() => {
|
|
|
34358
34658
|
create_space: "Create space and auto-join. Required: name. Optional: from?, description?, parent_id?(max 3 levels), project_id?",
|
|
34359
34659
|
list_spaces: "List spaces with member/message counts. Optional: project_id?, parent_id?(use 'null' for top-level), include_archived?",
|
|
34360
34660
|
send_to_space: "Post message to space. Required: space, content. Optional: from?, priority?(low|normal|high|urgent), blocking?",
|
|
34361
|
-
read_space: "Read messages in a space. Required: space. Optional: since?(ISO), limit?",
|
|
34661
|
+
read_space: "Read messages in a space. Required: space. Optional: since?(ISO), limit?, mark_read?(default true \u2014 auto-marks returned messages as read)",
|
|
34362
34662
|
join_space: "Join a space. Required: space. Optional: from?",
|
|
34363
34663
|
leave_space: "Leave a space. Required: space. Optional: from?",
|
|
34364
34664
|
update_space: "Update space fields. Required: name. Optional: description?, parent_id?(use 'null' to remove), project_id?(use 'null' to remove)",
|
|
@@ -34387,10 +34687,12 @@ var init_mcp2 = __esm(() => {
|
|
|
34387
34687
|
remove_reaction: "Remove emoji reaction from a message. Required: message_id, emoji. Optional: from?",
|
|
34388
34688
|
get_reactions: "Get all reactions for a message. Required: message_id",
|
|
34389
34689
|
get_reaction_summary: "Get emoji counts + agent lists for a message. Required: message_id",
|
|
34390
|
-
acquire_lock: "Acquire advisory/exclusive lock on a resource. Required: resource_type, resource_id. Optional: lock_type?(advisory|exclusive), expiry_ms?, from?",
|
|
34690
|
+
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)",
|
|
34691
|
+
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.",
|
|
34391
34692
|
release_lock: "Release lock held by agent. Required: resource_type, resource_id. Optional: from?",
|
|
34392
34693
|
check_lock: "Check if resource is locked and who holds it. Required: resource_type, resource_id",
|
|
34393
|
-
list_locks: "List active locks. Optional: resource_type?, agent_id?",
|
|
34694
|
+
list_locks: "List active locks enriched with agent presence + time context. Optional: resource_type?, agent_id?",
|
|
34695
|
+
clean_expired_locks: "Release expired locks + locks held by agents with stale heartbeat (>30 min). Returns {released_stale_agent, released_expired, total}",
|
|
34394
34696
|
get_thread_replies: "Get all replies in a thread. Required: message_id. Optional: limit?",
|
|
34395
34697
|
set_focus: "Set agent focus to a project. All read tools default to this scope. Required: project_id. Optional: from?",
|
|
34396
34698
|
get_focus: "Get current focus: session focus, DB project_id, effective project_id. Optional: from?",
|
|
@@ -36039,6 +36341,33 @@ program2.command("read").description("Read messages").option("--session <id>", "
|
|
|
36039
36341
|
}
|
|
36040
36342
|
closeDb();
|
|
36041
36343
|
});
|
|
36344
|
+
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("--json", "Output as JSON").action((spaceArg, opts) => {
|
|
36345
|
+
const result = readDigest({
|
|
36346
|
+
space: spaceArg || undefined,
|
|
36347
|
+
since: opts.since,
|
|
36348
|
+
limit: opts.limit,
|
|
36349
|
+
to: opts.to
|
|
36350
|
+
});
|
|
36351
|
+
if (opts.json) {
|
|
36352
|
+
console.log(JSON.stringify(result, null, 2));
|
|
36353
|
+
} else {
|
|
36354
|
+
console.log(chalk3.bold(`Unread: ${result.total_unread} total, showing ${result.shown}`));
|
|
36355
|
+
if (result.messages.length === 0) {
|
|
36356
|
+
console.log(chalk3.dim(" No unread messages."));
|
|
36357
|
+
} else {
|
|
36358
|
+
for (const msg of result.messages) {
|
|
36359
|
+
const time3 = chalk3.dim(msg.created_at.slice(11, 19));
|
|
36360
|
+
const from = chalk3.cyan(msg.from);
|
|
36361
|
+
const dest = msg.space ? chalk3.magenta(`#${msg.space}`) : chalk3.yellow(msg.to ?? "?");
|
|
36362
|
+
const priority = msg.priority !== "normal" ? chalk3.red(` [${msg.priority}]`) : "";
|
|
36363
|
+
const att = msg.has_attachments ? chalk3.dim(" \uD83D\uDCCE") : "";
|
|
36364
|
+
console.log(`${time3} ${from} \u2192 ${dest}${priority}${att}`);
|
|
36365
|
+
console.log(` ${chalk3.dim(msg.preview)}`);
|
|
36366
|
+
}
|
|
36367
|
+
}
|
|
36368
|
+
}
|
|
36369
|
+
closeDb();
|
|
36370
|
+
});
|
|
36042
36371
|
program2.command("search").description("Search messages by content").argument("<query>", "Search query string").option("--space <name>", "Filter by space").option("--from <agent>", "Filter by sender").option("--to <agent>", "Filter by recipient").option("--limit <n>", "Max results to return", parseInt).option("--json", "Output as JSON").action((query, opts) => {
|
|
36043
36372
|
const q = typeof query === "string" ? query.trim() : "";
|
|
36044
36373
|
if (!q) {
|
|
@@ -37124,6 +37453,85 @@ agents.command("rename").description("Rename an agent in the presence list").arg
|
|
|
37124
37453
|
}
|
|
37125
37454
|
closeDb();
|
|
37126
37455
|
});
|
|
37456
|
+
agents.command("register").description("Register an agent with conflict detection (30 min active window)").argument("<name>", "Agent name to register").option("--session <id>", "Session ID (default: random UUID)").option("--role <role>", "Agent role (default: agent)").option("--project <id>", "Project ID to lock agent to").option("--force", "Force takeover even if another session is active").option("--json", "Output as JSON").action((name, opts) => {
|
|
37457
|
+
const agentName = (typeof name === "string" ? name : "").trim();
|
|
37458
|
+
if (!agentName) {
|
|
37459
|
+
console.error(chalk3.red("Agent name is required."));
|
|
37460
|
+
process.exit(1);
|
|
37461
|
+
}
|
|
37462
|
+
const sessionId = opts.session || crypto.randomUUID();
|
|
37463
|
+
const result = registerAgent(agentName, sessionId, opts.role, opts.project);
|
|
37464
|
+
if (isAgentConflict(result)) {
|
|
37465
|
+
if (opts.json) {
|
|
37466
|
+
console.log(JSON.stringify(result));
|
|
37467
|
+
} else {
|
|
37468
|
+
console.error(chalk3.red(`Conflict: agent "${agentName}" is already active (last seen: ${result.last_seen_at}).`));
|
|
37469
|
+
console.error(chalk3.dim("Use --force or wait 30 minutes for the session to expire."));
|
|
37470
|
+
}
|
|
37471
|
+
process.exit(1);
|
|
37472
|
+
}
|
|
37473
|
+
if (opts.json) {
|
|
37474
|
+
console.log(JSON.stringify(result));
|
|
37475
|
+
} else {
|
|
37476
|
+
const action = result.took_over ? chalk3.yellow("took over") : result.created ? chalk3.green("registered") : chalk3.cyan("updated");
|
|
37477
|
+
console.log(` ${action} ${chalk3.bold(result.agent.agent)} session: ${chalk3.dim(sessionId)}`);
|
|
37478
|
+
}
|
|
37479
|
+
closeDb();
|
|
37480
|
+
});
|
|
37481
|
+
agents.command("heartbeat").description("Send a presence heartbeat to mark yourself as active").option("--from <agent>", "Agent identity (default: CONVERSATIONS_AGENT_ID or auto)").option("--status <status>", "Status: online, busy, idle (default: online)").option("--json", "Output as JSON").action((opts) => {
|
|
37482
|
+
const agent = resolveIdentity(opts.from);
|
|
37483
|
+
const status = opts.status || "online";
|
|
37484
|
+
heartbeat(agent, status);
|
|
37485
|
+
if (opts.json) {
|
|
37486
|
+
console.log(JSON.stringify({ agent, status, heartbeat: true }));
|
|
37487
|
+
} else {
|
|
37488
|
+
console.log(` ${chalk3.green("\u2665")} ${chalk3.cyan(agent)} ${chalk3.dim(status)}`);
|
|
37489
|
+
}
|
|
37490
|
+
closeDb();
|
|
37491
|
+
});
|
|
37492
|
+
var focus = program2.command("focus").description("Manage agent project focus");
|
|
37493
|
+
focus.command("set").description("Set your project focus \u2014 scopes read operations to this project").argument("<project>", "Project ID or name").option("--from <agent>", "Agent identity").option("--json", "Output as JSON").action((projectArg, opts) => {
|
|
37494
|
+
const agent = resolveIdentity(opts.from);
|
|
37495
|
+
const project2 = getProject(projectArg) || getProjectByName(projectArg);
|
|
37496
|
+
if (!project2) {
|
|
37497
|
+
console.error(chalk3.red(`Project "${projectArg}" not found.`));
|
|
37498
|
+
process.exit(1);
|
|
37499
|
+
}
|
|
37500
|
+
getDb().prepare("UPDATE agent_presence SET project_id = ? WHERE agent = ?").run(project2.id, agent);
|
|
37501
|
+
if (opts.json) {
|
|
37502
|
+
console.log(JSON.stringify({ agent, project_id: project2.id, project_name: project2.name, focused: true }));
|
|
37503
|
+
} else {
|
|
37504
|
+
console.log(` ${chalk3.green("focused")} ${chalk3.cyan(agent)} \u2192 ${chalk3.bold(project2.name)} ${chalk3.dim(`(${project2.id})`)}`);
|
|
37505
|
+
}
|
|
37506
|
+
closeDb();
|
|
37507
|
+
});
|
|
37508
|
+
focus.command("clear").description("Clear your project focus").option("--from <agent>", "Agent identity").option("--json", "Output as JSON").action((opts) => {
|
|
37509
|
+
const agent = resolveIdentity(opts.from);
|
|
37510
|
+
getDb().prepare("UPDATE agent_presence SET project_id = NULL WHERE agent = ?").run(agent);
|
|
37511
|
+
if (opts.json) {
|
|
37512
|
+
console.log(JSON.stringify({ agent, project_id: null, focused: false }));
|
|
37513
|
+
} else {
|
|
37514
|
+
console.log(` ${chalk3.yellow("unfocused")} ${chalk3.cyan(agent)}`);
|
|
37515
|
+
}
|
|
37516
|
+
closeDb();
|
|
37517
|
+
});
|
|
37518
|
+
focus.command("get").description("Show current project focus").option("--from <agent>", "Agent identity").option("--json", "Output as JSON").action((opts) => {
|
|
37519
|
+
const agent = resolveIdentity(opts.from);
|
|
37520
|
+
const presence = getPresence(agent);
|
|
37521
|
+
const projectId = presence?.project_id ?? null;
|
|
37522
|
+
const project2 = projectId ? getProject(projectId) || null : null;
|
|
37523
|
+
if (opts.json) {
|
|
37524
|
+
console.log(JSON.stringify({ agent, project_id: projectId, project_name: project2?.name ?? null }));
|
|
37525
|
+
} else {
|
|
37526
|
+
if (projectId) {
|
|
37527
|
+
const name = project2?.name ?? chalk3.dim("(unknown)");
|
|
37528
|
+
console.log(` ${chalk3.cyan(agent)} focused on ${chalk3.bold(name)} ${chalk3.dim(`(${projectId})`)}`);
|
|
37529
|
+
} else {
|
|
37530
|
+
console.log(` ${chalk3.cyan(agent)} ${chalk3.dim("no focus set")}`);
|
|
37531
|
+
}
|
|
37532
|
+
}
|
|
37533
|
+
closeDb();
|
|
37534
|
+
});
|
|
37127
37535
|
program2.command("whoami").description("Show current agent identity and online status").option("--from <agent>", "Explicit agent identity").action((opts) => {
|
|
37128
37536
|
const envValue = process.env.CONVERSATIONS_AGENT_ID?.trim();
|
|
37129
37537
|
const agent = resolveIdentity(opts.from);
|