@hasna/conversations 0.2.42 → 0.2.44
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 +170 -20
- package/bin/index.js +478 -130
- package/bin/mcp.js +455 -107
- package/dist/index.js +385 -91
- package/dist/lib/messages.d.ts +1 -1
- package/dist/lib/presence.d.ts +2 -1
- package/dist/lib/webhooks.d.ts +1 -0
- package/package.json +1 -1
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;
|
|
@@ -6515,7 +6536,7 @@ var require_dist = __commonJS((exports, module) => {
|
|
|
6515
6536
|
exports.default = formatsPlugin;
|
|
6516
6537
|
});
|
|
6517
6538
|
|
|
6518
|
-
//
|
|
6539
|
+
// node_modules/@hasna/cloud/dist/index.js
|
|
6519
6540
|
var exports_dist = {};
|
|
6520
6541
|
__export(exports_dist, {
|
|
6521
6542
|
translateSql: () => translateSql,
|
|
@@ -6610,11 +6631,11 @@ import { join as join5 } from "path";
|
|
|
6610
6631
|
import { join as join6, dirname } from "path";
|
|
6611
6632
|
import { existsSync as existsSync6, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
|
|
6612
6633
|
import { homedir as homedir5, platform } from "os";
|
|
6613
|
-
function
|
|
6634
|
+
function __accessProp2(key) {
|
|
6614
6635
|
return this[key];
|
|
6615
6636
|
}
|
|
6616
|
-
function
|
|
6617
|
-
this[name] =
|
|
6637
|
+
function __exportSetter2(name, newValue) {
|
|
6638
|
+
this[name] = __returnValue2.bind(null, newValue);
|
|
6618
6639
|
}
|
|
6619
6640
|
function translateSql(sql, dialect) {
|
|
6620
6641
|
if (dialect === "sqlite")
|
|
@@ -7927,9 +7948,9 @@ async function syncTransfer(source, target, options, _direction) {
|
|
|
7927
7948
|
const batch = rows.slice(offset, offset + batchSize);
|
|
7928
7949
|
try {
|
|
7929
7950
|
if (isAsyncAdapter(target)) {
|
|
7930
|
-
await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch
|
|
7951
|
+
await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
|
|
7931
7952
|
} else {
|
|
7932
|
-
batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch
|
|
7953
|
+
batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
|
|
7933
7954
|
}
|
|
7934
7955
|
result.rowsWritten += batch.length;
|
|
7935
7956
|
} catch (err) {
|
|
@@ -7976,7 +7997,7 @@ async function syncTransfer(source, target, options, _direction) {
|
|
|
7976
7997
|
}
|
|
7977
7998
|
return results;
|
|
7978
7999
|
}
|
|
7979
|
-
async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch
|
|
8000
|
+
async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch) {
|
|
7980
8001
|
if (batch.length === 0)
|
|
7981
8002
|
return;
|
|
7982
8003
|
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
@@ -7986,22 +8007,20 @@ async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, ba
|
|
|
7986
8007
|
}).join(", ");
|
|
7987
8008
|
const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
|
|
7988
8009
|
const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
|
|
7989
|
-
const whereClause = conflictColumn && updateCols.includes(conflictColumn) ? ` WHERE "${table}"."${conflictColumn}" IS NULL OR EXCLUDED."${conflictColumn}" >= "${table}"."${conflictColumn}"` : "";
|
|
7990
8010
|
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
|
|
7991
|
-
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}
|
|
8011
|
+
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
|
|
7992
8012
|
const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
|
|
7993
8013
|
await target.run(sql, ...params);
|
|
7994
8014
|
}
|
|
7995
|
-
function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch
|
|
8015
|
+
function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch) {
|
|
7996
8016
|
if (batch.length === 0)
|
|
7997
8017
|
return;
|
|
7998
8018
|
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
7999
8019
|
const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
|
|
8000
8020
|
const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
|
|
8001
8021
|
const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
|
|
8002
|
-
const whereClause = conflictColumn && updateCols.includes(conflictColumn) ? ` WHERE "${table}"."${conflictColumn}" IS NULL OR EXCLUDED."${conflictColumn}" >= "${table}"."${conflictColumn}"` : "";
|
|
8003
8022
|
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
|
|
8004
|
-
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}
|
|
8023
|
+
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
|
|
8005
8024
|
const params = batch.flatMap((row) => columns.map((c) => coerceForSqlite(row[c])));
|
|
8006
8025
|
target.run(sql, ...params);
|
|
8007
8026
|
}
|
|
@@ -9071,7 +9090,7 @@ async function ensureAllPgDatabases() {
|
|
|
9071
9090
|
}
|
|
9072
9091
|
return results;
|
|
9073
9092
|
}
|
|
9074
|
-
function registerCloudTools(server, serviceName
|
|
9093
|
+
function registerCloudTools(server, serviceName) {
|
|
9075
9094
|
server.tool(`${serviceName}_cloud_status`, "Show cloud configuration and connection health", {}, async () => {
|
|
9076
9095
|
const config2 = getCloudConfig();
|
|
9077
9096
|
const lines = [
|
|
@@ -9104,13 +9123,8 @@ function registerCloudTools(server, serviceName, opts = {}) {
|
|
|
9104
9123
|
isError: true
|
|
9105
9124
|
};
|
|
9106
9125
|
}
|
|
9107
|
-
const local = new SqliteAdapter(
|
|
9126
|
+
const local = new SqliteAdapter(getDbPath(serviceName));
|
|
9108
9127
|
const cloud = new PgAdapterAsync(getConnectionString(serviceName));
|
|
9109
|
-
if (opts.migrations?.length) {
|
|
9110
|
-
for (const sql of opts.migrations) {
|
|
9111
|
-
await cloud.run(sql);
|
|
9112
|
-
}
|
|
9113
|
-
}
|
|
9114
9128
|
const tableList = tablesStr ? tablesStr.split(",").map((t) => t.trim()) : listSqliteTables(local);
|
|
9115
9129
|
const results = await syncPush(local, cloud, { tables: tableList });
|
|
9116
9130
|
local.close();
|
|
@@ -9132,7 +9146,7 @@ function registerCloudTools(server, serviceName, opts = {}) {
|
|
|
9132
9146
|
isError: true
|
|
9133
9147
|
};
|
|
9134
9148
|
}
|
|
9135
|
-
const local = new SqliteAdapter(
|
|
9149
|
+
const local = new SqliteAdapter(getDbPath(serviceName));
|
|
9136
9150
|
const cloud = new PgAdapterAsync(getConnectionString(serviceName));
|
|
9137
9151
|
let tableList;
|
|
9138
9152
|
if (tablesStr) {
|
|
@@ -9255,10 +9269,10 @@ function registerCloudCommands(program, serviceName) {
|
|
|
9255
9269
|
}
|
|
9256
9270
|
});
|
|
9257
9271
|
}
|
|
9258
|
-
var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2,
|
|
9272
|
+
var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __toESMCache_node2, __toESMCache_esm2, __toESM2 = (mod, isNodeMode, target) => {
|
|
9259
9273
|
var canCache = mod != null && typeof mod === "object";
|
|
9260
9274
|
if (canCache) {
|
|
9261
|
-
var cache = isNodeMode ?
|
|
9275
|
+
var cache = isNodeMode ? __toESMCache_node2 ??= new WeakMap : __toESMCache_esm2 ??= new WeakMap;
|
|
9262
9276
|
var cached2 = cache.get(mod);
|
|
9263
9277
|
if (cached2)
|
|
9264
9278
|
return cached2;
|
|
@@ -9268,19 +9282,19 @@ var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __t
|
|
|
9268
9282
|
for (let key of __getOwnPropNames2(mod))
|
|
9269
9283
|
if (!__hasOwnProp2.call(to, key))
|
|
9270
9284
|
__defProp2(to, key, {
|
|
9271
|
-
get:
|
|
9285
|
+
get: __accessProp2.bind(mod, key),
|
|
9272
9286
|
enumerable: true
|
|
9273
9287
|
});
|
|
9274
9288
|
if (canCache)
|
|
9275
9289
|
cache.set(mod, to);
|
|
9276
9290
|
return to;
|
|
9277
|
-
}, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports),
|
|
9291
|
+
}, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __returnValue2 = (v) => v, __export2 = (target, all) => {
|
|
9278
9292
|
for (var name in all)
|
|
9279
9293
|
__defProp2(target, name, {
|
|
9280
9294
|
get: all[name],
|
|
9281
9295
|
enumerable: true,
|
|
9282
9296
|
configurable: true,
|
|
9283
|
-
set:
|
|
9297
|
+
set: __exportSetter2.bind(all, name)
|
|
9284
9298
|
});
|
|
9285
9299
|
}, __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), __require, require_postgres_array, require_arrayParser, require_postgres_date, require_mutable, require_postgres_interval, require_postgres_bytea, require_textParsers, require_pg_int8, require_binaryParsers, require_builtins, require_pg_types, require_defaults2, require_utils2, require_utils_legacy, require_utils_webcrypto, require_utils22, require_cert_signatures, require_sasl, require_type_overrides, require_pg_connection_string, require_connection_parameters, require_result, require_query, require_messages, require_buffer_writer, require_serializer, require_buffer_reader, require_parser, require_dist2, require_empty, require_stream, require_connection, require_split2, require_helper, require_lib, require_client, require_pg_pool, require_query2, require_client2, require_lib2, import_lib, Client, Pool, Connection, types, Query, DatabaseError, escapeIdentifier, escapeLiteral, Result, TypeOverrides, defaults, esm_default, init_esm, init_adapter, util2, objectUtil2, ZodParsedType2, getParsedType3 = (data) => {
|
|
9286
9300
|
const t = typeof data;
|
|
@@ -17637,6 +17651,137 @@ function getDbPath2() {
|
|
|
17637
17651
|
return process.env.CONVERSATIONS_DB_PATH;
|
|
17638
17652
|
return join7(getDataDir2(), "messages.db");
|
|
17639
17653
|
}
|
|
17654
|
+
function parsePresenceTimestamp(value) {
|
|
17655
|
+
if (typeof value !== "string" || !value)
|
|
17656
|
+
return 0;
|
|
17657
|
+
return new Date(`${value}Z`).getTime() || 0;
|
|
17658
|
+
}
|
|
17659
|
+
function normalizePresenceText(value) {
|
|
17660
|
+
if (typeof value !== "string")
|
|
17661
|
+
return null;
|
|
17662
|
+
const normalized = value.trim();
|
|
17663
|
+
return normalized ? normalized : null;
|
|
17664
|
+
}
|
|
17665
|
+
function shouldRebuildAgentPresenceTable(columns) {
|
|
17666
|
+
const byName = new Map(columns.map((column) => [column.name, column]));
|
|
17667
|
+
const agentCol = byName.get("agent");
|
|
17668
|
+
const projectCol = byName.get("project_id");
|
|
17669
|
+
if (!agentCol)
|
|
17670
|
+
return false;
|
|
17671
|
+
if (!projectCol)
|
|
17672
|
+
return true;
|
|
17673
|
+
return agentCol.pk !== 1 || projectCol.pk !== 2 || projectCol.notnull !== 1 || byName.has("pid");
|
|
17674
|
+
}
|
|
17675
|
+
function rebuildLegacyAgentPresenceTable(db2) {
|
|
17676
|
+
const fallbackNow = db2.prepare("SELECT strftime('%Y-%m-%dT%H:%M:%f', 'now') AS now").get().now;
|
|
17677
|
+
const legacyRows = db2.prepare("SELECT rowid AS _rowid, * FROM agent_presence").all();
|
|
17678
|
+
legacyRows.sort((left, right) => {
|
|
17679
|
+
const lastSeenDelta = parsePresenceTimestamp(right.last_seen_at) - parsePresenceTimestamp(left.last_seen_at);
|
|
17680
|
+
if (lastSeenDelta !== 0)
|
|
17681
|
+
return lastSeenDelta;
|
|
17682
|
+
const createdDelta = parsePresenceTimestamp(right.created_at) - parsePresenceTimestamp(left.created_at);
|
|
17683
|
+
if (createdDelta !== 0)
|
|
17684
|
+
return createdDelta;
|
|
17685
|
+
const projectDelta = Number(Boolean(normalizePresenceText(right.project_id))) - Number(Boolean(normalizePresenceText(left.project_id)));
|
|
17686
|
+
if (projectDelta !== 0)
|
|
17687
|
+
return projectDelta;
|
|
17688
|
+
return right._rowid - left._rowid;
|
|
17689
|
+
});
|
|
17690
|
+
const dedupedRows = new Map;
|
|
17691
|
+
for (const row of legacyRows) {
|
|
17692
|
+
const normalizedAgent = normalizePresenceText(row.agent)?.toLowerCase();
|
|
17693
|
+
if (!normalizedAgent)
|
|
17694
|
+
continue;
|
|
17695
|
+
const storedProjectId = normalizePresenceText(row.project_id) ?? "";
|
|
17696
|
+
const dedupeKey = `${normalizedAgent}\x00${storedProjectId}`;
|
|
17697
|
+
if (dedupedRows.has(dedupeKey))
|
|
17698
|
+
continue;
|
|
17699
|
+
dedupedRows.set(dedupeKey, row);
|
|
17700
|
+
}
|
|
17701
|
+
db2.exec("BEGIN");
|
|
17702
|
+
try {
|
|
17703
|
+
db2.exec(`
|
|
17704
|
+
CREATE TABLE agent_presence_new (
|
|
17705
|
+
id TEXT NOT NULL,
|
|
17706
|
+
agent TEXT NOT NULL,
|
|
17707
|
+
session_id TEXT,
|
|
17708
|
+
role TEXT NOT NULL DEFAULT 'agent',
|
|
17709
|
+
project_id TEXT NOT NULL DEFAULT '',
|
|
17710
|
+
status TEXT NOT NULL DEFAULT 'online',
|
|
17711
|
+
last_seen_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
17712
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
17713
|
+
metadata TEXT,
|
|
17714
|
+
PRIMARY KEY (agent, project_id)
|
|
17715
|
+
)
|
|
17716
|
+
`);
|
|
17717
|
+
const insertPresence = db2.prepare(`
|
|
17718
|
+
INSERT INTO agent_presence_new (id, agent, session_id, role, project_id, status, last_seen_at, created_at, metadata)
|
|
17719
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
17720
|
+
`);
|
|
17721
|
+
for (const [dedupeKey, row] of dedupedRows) {
|
|
17722
|
+
const [agent, projectKey] = dedupeKey.split("\x00");
|
|
17723
|
+
const id = normalizePresenceText(row.id) ?? crypto.randomUUID().slice(0, 8);
|
|
17724
|
+
const sessionId = normalizePresenceText(row.session_id);
|
|
17725
|
+
const role = normalizePresenceText(row.role) ?? "agent";
|
|
17726
|
+
const projectId = projectKey;
|
|
17727
|
+
const status = normalizePresenceText(row.status) ?? "online";
|
|
17728
|
+
const lastSeenAt = normalizePresenceText(row.last_seen_at) ?? fallbackNow;
|
|
17729
|
+
const createdAt = normalizePresenceText(row.created_at) ?? lastSeenAt;
|
|
17730
|
+
const metadata = typeof row.metadata === "string" ? row.metadata : row.metadata == null ? null : JSON.stringify(row.metadata);
|
|
17731
|
+
insertPresence.run(id, agent, sessionId, role, projectId, status, lastSeenAt, createdAt, metadata);
|
|
17732
|
+
}
|
|
17733
|
+
db2.exec("DROP TABLE agent_presence");
|
|
17734
|
+
db2.exec("ALTER TABLE agent_presence_new RENAME TO agent_presence");
|
|
17735
|
+
db2.exec("COMMIT");
|
|
17736
|
+
} catch (error48) {
|
|
17737
|
+
db2.exec("ROLLBACK");
|
|
17738
|
+
throw error48;
|
|
17739
|
+
}
|
|
17740
|
+
}
|
|
17741
|
+
function collapseDuplicateAgentPresenceRows(db2) {
|
|
17742
|
+
const rows = db2.prepare("SELECT rowid AS _rowid, * FROM agent_presence").all();
|
|
17743
|
+
rows.sort((left, right) => {
|
|
17744
|
+
const lastSeenDelta = parsePresenceTimestamp(right.last_seen_at) - parsePresenceTimestamp(left.last_seen_at);
|
|
17745
|
+
if (lastSeenDelta !== 0)
|
|
17746
|
+
return lastSeenDelta;
|
|
17747
|
+
const createdDelta = parsePresenceTimestamp(right.created_at) - parsePresenceTimestamp(left.created_at);
|
|
17748
|
+
if (createdDelta !== 0)
|
|
17749
|
+
return createdDelta;
|
|
17750
|
+
const projectDelta = Number(Boolean(normalizePresenceText(right.project_id))) - Number(Boolean(normalizePresenceText(left.project_id)));
|
|
17751
|
+
if (projectDelta !== 0)
|
|
17752
|
+
return projectDelta;
|
|
17753
|
+
return right._rowid - left._rowid;
|
|
17754
|
+
});
|
|
17755
|
+
const rowIdsToDelete = [];
|
|
17756
|
+
const seenAgents = new Set;
|
|
17757
|
+
for (const row of rows) {
|
|
17758
|
+
const normalizedAgent = normalizePresenceText(row.agent)?.toLowerCase();
|
|
17759
|
+
if (!normalizedAgent)
|
|
17760
|
+
continue;
|
|
17761
|
+
if (seenAgents.has(normalizedAgent)) {
|
|
17762
|
+
rowIdsToDelete.push(row._rowid);
|
|
17763
|
+
continue;
|
|
17764
|
+
}
|
|
17765
|
+
seenAgents.add(normalizedAgent);
|
|
17766
|
+
}
|
|
17767
|
+
if (rowIdsToDelete.length === 0)
|
|
17768
|
+
return;
|
|
17769
|
+
db2.exec("BEGIN");
|
|
17770
|
+
try {
|
|
17771
|
+
const deleteRow = db2.prepare("DELETE FROM agent_presence WHERE rowid = ?");
|
|
17772
|
+
for (const rowId of rowIdsToDelete) {
|
|
17773
|
+
deleteRow.run(rowId);
|
|
17774
|
+
}
|
|
17775
|
+
db2.exec("COMMIT");
|
|
17776
|
+
} catch (error48) {
|
|
17777
|
+
db2.exec("ROLLBACK");
|
|
17778
|
+
throw error48;
|
|
17779
|
+
}
|
|
17780
|
+
}
|
|
17781
|
+
function ensureAgentPresenceAgentUniqueIndex(db2) {
|
|
17782
|
+
collapseDuplicateAgentPresenceRows(db2);
|
|
17783
|
+
db2.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_presence_agent_unique ON agent_presence(agent)");
|
|
17784
|
+
}
|
|
17640
17785
|
function getDb() {
|
|
17641
17786
|
if (db)
|
|
17642
17787
|
return db;
|
|
@@ -17709,16 +17854,18 @@ function getDb() {
|
|
|
17709
17854
|
db.exec(`
|
|
17710
17855
|
CREATE TABLE IF NOT EXISTS agent_presence (
|
|
17711
17856
|
id TEXT NOT NULL,
|
|
17712
|
-
agent TEXT
|
|
17857
|
+
agent TEXT NOT NULL,
|
|
17713
17858
|
session_id TEXT,
|
|
17714
17859
|
role TEXT NOT NULL DEFAULT 'agent',
|
|
17715
|
-
project_id TEXT,
|
|
17860
|
+
project_id TEXT NOT NULL DEFAULT '',
|
|
17716
17861
|
status TEXT NOT NULL DEFAULT 'online',
|
|
17717
17862
|
last_seen_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
17718
17863
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
17719
|
-
metadata TEXT
|
|
17864
|
+
metadata TEXT,
|
|
17865
|
+
PRIMARY KEY (agent, project_id)
|
|
17720
17866
|
)
|
|
17721
17867
|
`);
|
|
17868
|
+
ensureAgentPresenceAgentUniqueIndex(db);
|
|
17722
17869
|
db.exec(`
|
|
17723
17870
|
CREATE TABLE IF NOT EXISTS resource_locks (
|
|
17724
17871
|
resource_type TEXT NOT NULL,
|
|
@@ -17820,8 +17967,13 @@ function getDb() {
|
|
|
17820
17967
|
db.exec("UPDATE messages SET uuid = lower(hex(randomblob(16))) WHERE uuid IS NULL");
|
|
17821
17968
|
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_uuid ON messages(uuid)");
|
|
17822
17969
|
}
|
|
17823
|
-
|
|
17824
|
-
|
|
17970
|
+
let presenceCols = db.prepare("PRAGMA table_info(agent_presence)").all();
|
|
17971
|
+
let presenceColNames = presenceCols.map((c) => c.name);
|
|
17972
|
+
if (shouldRebuildAgentPresenceTable(presenceCols)) {
|
|
17973
|
+
rebuildLegacyAgentPresenceTable(db);
|
|
17974
|
+
presenceCols = db.prepare("PRAGMA table_info(agent_presence)").all();
|
|
17975
|
+
presenceColNames = presenceCols.map((c) => c.name);
|
|
17976
|
+
}
|
|
17825
17977
|
if (!presenceColNames.includes("id")) {
|
|
17826
17978
|
db.exec("ALTER TABLE agent_presence ADD COLUMN id TEXT NOT NULL DEFAULT ''");
|
|
17827
17979
|
const rows = db.prepare("SELECT agent FROM agent_presence").all();
|
|
@@ -17842,7 +17994,9 @@ function getDb() {
|
|
|
17842
17994
|
}
|
|
17843
17995
|
if (!presenceColNames.includes("project_id")) {
|
|
17844
17996
|
db.exec("ALTER TABLE agent_presence ADD COLUMN project_id TEXT");
|
|
17997
|
+
db.exec("UPDATE agent_presence SET project_id = '' WHERE project_id IS NULL");
|
|
17845
17998
|
}
|
|
17999
|
+
ensureAgentPresenceAgentUniqueIndex(db);
|
|
17846
18000
|
db.exec(`
|
|
17847
18001
|
CREATE TABLE IF NOT EXISTS message_read_receipts (
|
|
17848
18002
|
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
@@ -18437,15 +18591,17 @@ var init_pg_migrations = __esm(() => {
|
|
|
18437
18591
|
|
|
18438
18592
|
CREATE TABLE IF NOT EXISTS agent_presence (
|
|
18439
18593
|
id TEXT NOT NULL DEFAULT '',
|
|
18440
|
-
agent TEXT
|
|
18594
|
+
agent TEXT NOT NULL,
|
|
18441
18595
|
session_id TEXT,
|
|
18442
18596
|
role TEXT NOT NULL DEFAULT 'agent',
|
|
18443
|
-
project_id TEXT,
|
|
18597
|
+
project_id TEXT NOT NULL DEFAULT '',
|
|
18444
18598
|
status TEXT NOT NULL DEFAULT 'online',
|
|
18445
18599
|
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
18446
18600
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
18447
|
-
metadata TEXT
|
|
18601
|
+
metadata TEXT,
|
|
18602
|
+
PRIMARY KEY (agent, project_id)
|
|
18448
18603
|
);
|
|
18604
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_presence_agent_unique ON agent_presence(agent);
|
|
18449
18605
|
|
|
18450
18606
|
CREATE TABLE IF NOT EXISTS resource_locks (
|
|
18451
18607
|
resource_type TEXT NOT NULL,
|
|
@@ -40361,6 +40517,33 @@ class StdioServerTransport {
|
|
|
40361
40517
|
init_db();
|
|
40362
40518
|
var ONLINE_THRESHOLD_SECONDS = 60;
|
|
40363
40519
|
var CONFLICT_THRESHOLD_SECONDS = 30 * 60;
|
|
40520
|
+
function normalizeAgentName(name) {
|
|
40521
|
+
return name.trim().toLowerCase();
|
|
40522
|
+
}
|
|
40523
|
+
function toStoredProjectId(projectId) {
|
|
40524
|
+
const normalized = projectId?.trim() ?? "";
|
|
40525
|
+
return normalized || "";
|
|
40526
|
+
}
|
|
40527
|
+
function fromStoredProjectId(projectId) {
|
|
40528
|
+
const normalized = typeof projectId === "string" ? projectId.trim() : "";
|
|
40529
|
+
return normalized || null;
|
|
40530
|
+
}
|
|
40531
|
+
function getPresenceByAgent(db2, agent) {
|
|
40532
|
+
return db2.prepare(`
|
|
40533
|
+
SELECT * FROM agent_presence
|
|
40534
|
+
WHERE LOWER(agent) = ?
|
|
40535
|
+
ORDER BY last_seen_at DESC
|
|
40536
|
+
LIMIT 1
|
|
40537
|
+
`).get(agent);
|
|
40538
|
+
}
|
|
40539
|
+
function getPresenceByAgentAndProject(db2, agent, projectId) {
|
|
40540
|
+
return db2.prepare(`
|
|
40541
|
+
SELECT * FROM agent_presence
|
|
40542
|
+
WHERE LOWER(agent) = ? AND COALESCE(project_id, '') = ?
|
|
40543
|
+
ORDER BY last_seen_at DESC
|
|
40544
|
+
LIMIT 1
|
|
40545
|
+
`).get(agent, projectId);
|
|
40546
|
+
}
|
|
40364
40547
|
function parsePresence(row) {
|
|
40365
40548
|
let metadata = null;
|
|
40366
40549
|
if (row.metadata) {
|
|
@@ -40379,7 +40562,7 @@ function parsePresence(row) {
|
|
|
40379
40562
|
agent: row.agent,
|
|
40380
40563
|
session_id: row.session_id ?? null,
|
|
40381
40564
|
role: row.role || "agent",
|
|
40382
|
-
project_id: row.project_id
|
|
40565
|
+
project_id: fromStoredProjectId(row.project_id),
|
|
40383
40566
|
status: row.status,
|
|
40384
40567
|
last_seen_at: lastSeenAt,
|
|
40385
40568
|
created_at: row.created_at || lastSeenAt,
|
|
@@ -40394,9 +40577,10 @@ function isActiveSession(lastSeenAt) {
|
|
|
40394
40577
|
}
|
|
40395
40578
|
function registerAgent(name, sessionId, role, projectId) {
|
|
40396
40579
|
const db2 = getDb();
|
|
40397
|
-
const normalizedName = name
|
|
40580
|
+
const normalizedName = normalizeAgentName(name);
|
|
40398
40581
|
const result = db2.transaction(() => {
|
|
40399
|
-
const existing = db2
|
|
40582
|
+
const existing = getPresenceByAgent(db2, normalizedName);
|
|
40583
|
+
const storedProjectId = toStoredProjectId(projectId ?? existing?.project_id);
|
|
40400
40584
|
if (existing) {
|
|
40401
40585
|
const lastSeenAt = existing.last_seen_at;
|
|
40402
40586
|
const existingSessionId = existing.session_id;
|
|
@@ -40414,12 +40598,26 @@ function registerAgent(name, sessionId, role, projectId) {
|
|
|
40414
40598
|
};
|
|
40415
40599
|
}
|
|
40416
40600
|
const tookOver = existingSessionId !== sessionId;
|
|
40417
|
-
|
|
40418
|
-
|
|
40419
|
-
|
|
40420
|
-
|
|
40421
|
-
|
|
40422
|
-
|
|
40601
|
+
const existingId = existing.id;
|
|
40602
|
+
const target = getPresenceByAgentAndProject(db2, normalizedName, storedProjectId);
|
|
40603
|
+
if (target && target.id !== existingId) {
|
|
40604
|
+
db2.prepare(`
|
|
40605
|
+
UPDATE agent_presence
|
|
40606
|
+
SET session_id = ?, role = ?, status = 'online', last_seen_at = strftime('%Y-%m-%dT%H:%M:%f', 'now')
|
|
40607
|
+
WHERE id = ?
|
|
40608
|
+
`).run(sessionId, role || existing.role || "agent", target.id);
|
|
40609
|
+
db2.prepare("DELETE FROM agent_presence WHERE id = ?").run(existingId);
|
|
40610
|
+
} else {
|
|
40611
|
+
db2.prepare(`
|
|
40612
|
+
UPDATE agent_presence
|
|
40613
|
+
SET session_id = ?, role = ?, project_id = ?, status = 'online', last_seen_at = strftime('%Y-%m-%dT%H:%M:%f', 'now')
|
|
40614
|
+
WHERE id = ?
|
|
40615
|
+
`).run(sessionId, role || existing.role || "agent", storedProjectId, existingId);
|
|
40616
|
+
}
|
|
40617
|
+
const updated = getPresenceByAgentAndProject(db2, normalizedName, storedProjectId) ?? getPresenceByAgent(db2, normalizedName);
|
|
40618
|
+
if (!updated) {
|
|
40619
|
+
throw new Error(`Failed to update presence for agent "${normalizedName}"`);
|
|
40620
|
+
}
|
|
40423
40621
|
return { agent: parsePresence(updated), created: false, took_over: tookOver };
|
|
40424
40622
|
}
|
|
40425
40623
|
const id = crypto.randomUUID().slice(0, 8);
|
|
@@ -40427,33 +40625,60 @@ function registerAgent(name, sessionId, role, projectId) {
|
|
|
40427
40625
|
db2.prepare(`
|
|
40428
40626
|
INSERT INTO agent_presence (id, agent, session_id, role, project_id, status, last_seen_at, created_at)
|
|
40429
40627
|
VALUES (?, ?, ?, ?, ?, 'online', strftime('%Y-%m-%dT%H:%M:%f', 'now'), strftime('%Y-%m-%dT%H:%M:%f', 'now'))
|
|
40430
|
-
`).run(id, normalizedName, sessionId, resolvedRole,
|
|
40431
|
-
const created = db2
|
|
40628
|
+
`).run(id, normalizedName, sessionId, resolvedRole, storedProjectId);
|
|
40629
|
+
const created = getPresenceByAgentAndProject(db2, normalizedName, storedProjectId) ?? getPresenceByAgent(db2, normalizedName);
|
|
40630
|
+
if (!created) {
|
|
40631
|
+
throw new Error(`Failed to create presence for agent "${normalizedName}"`);
|
|
40632
|
+
}
|
|
40432
40633
|
return { agent: parsePresence(created), created: true, took_over: false };
|
|
40433
40634
|
});
|
|
40434
40635
|
return result;
|
|
40435
40636
|
}
|
|
40436
|
-
function heartbeat(agent, status, metadata, sessionId) {
|
|
40637
|
+
function heartbeat(agent, status, metadata, sessionId, projectId) {
|
|
40437
40638
|
const db2 = getDb();
|
|
40438
40639
|
const metadataJson = metadata ? JSON.stringify(metadata) : null;
|
|
40439
40640
|
const resolvedStatus = status || "online";
|
|
40440
|
-
const normalizedAgent = agent
|
|
40441
|
-
|
|
40442
|
-
|
|
40443
|
-
|
|
40444
|
-
|
|
40445
|
-
|
|
40446
|
-
|
|
40447
|
-
|
|
40448
|
-
|
|
40449
|
-
|
|
40450
|
-
|
|
40451
|
-
|
|
40641
|
+
const normalizedAgent = normalizeAgentName(agent);
|
|
40642
|
+
db2.transaction(() => {
|
|
40643
|
+
const existing = getPresenceByAgent(db2, normalizedAgent);
|
|
40644
|
+
const storedProjectId = toStoredProjectId(projectId ?? existing?.project_id);
|
|
40645
|
+
const id = existing?.id || crypto.randomUUID().slice(0, 8);
|
|
40646
|
+
if (existing) {
|
|
40647
|
+
const existingId = existing.id;
|
|
40648
|
+
const target = getPresenceByAgentAndProject(db2, normalizedAgent, storedProjectId);
|
|
40649
|
+
if (target && target.id !== existingId) {
|
|
40650
|
+
db2.prepare(`
|
|
40651
|
+
UPDATE agent_presence
|
|
40652
|
+
SET status = ?,
|
|
40653
|
+
last_seen_at = strftime('%Y-%m-%dT%H:%M:%f', 'now'),
|
|
40654
|
+
session_id = COALESCE(?, session_id),
|
|
40655
|
+
metadata = ?
|
|
40656
|
+
WHERE id = ?
|
|
40657
|
+
`).run(resolvedStatus, sessionId ?? null, metadataJson, target.id);
|
|
40658
|
+
db2.prepare("DELETE FROM agent_presence WHERE id = ?").run(existingId);
|
|
40659
|
+
return;
|
|
40660
|
+
}
|
|
40661
|
+
db2.prepare(`
|
|
40662
|
+
UPDATE agent_presence
|
|
40663
|
+
SET status = ?,
|
|
40664
|
+
last_seen_at = strftime('%Y-%m-%dT%H:%M:%f', 'now'),
|
|
40665
|
+
session_id = COALESCE(?, session_id),
|
|
40666
|
+
metadata = ?,
|
|
40667
|
+
project_id = ?
|
|
40668
|
+
WHERE id = ?
|
|
40669
|
+
`).run(resolvedStatus, sessionId ?? null, metadataJson, storedProjectId, existingId);
|
|
40670
|
+
return;
|
|
40671
|
+
}
|
|
40672
|
+
db2.prepare(`
|
|
40673
|
+
INSERT INTO agent_presence (id, agent, session_id, role, project_id, status, last_seen_at, created_at, metadata)
|
|
40674
|
+
VALUES (?, ?, ?, 'agent', ?, ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'), strftime('%Y-%m-%dT%H:%M:%f', 'now'), ?)
|
|
40675
|
+
`).run(id, normalizedAgent, sessionId ?? null, storedProjectId, resolvedStatus, metadataJson);
|
|
40676
|
+
});
|
|
40452
40677
|
}
|
|
40453
40678
|
function getPresence(agent) {
|
|
40454
40679
|
const db2 = getDb();
|
|
40455
|
-
const normalizedAgent = agent
|
|
40456
|
-
const row = db2
|
|
40680
|
+
const normalizedAgent = normalizeAgentName(agent);
|
|
40681
|
+
const row = getPresenceByAgent(db2, normalizedAgent);
|
|
40457
40682
|
return row ? parsePresence(row) : null;
|
|
40458
40683
|
}
|
|
40459
40684
|
function listAgents(opts) {
|
|
@@ -40468,14 +40693,14 @@ function listAgents(opts) {
|
|
|
40468
40693
|
}
|
|
40469
40694
|
function removePresence(agent) {
|
|
40470
40695
|
const db2 = getDb();
|
|
40471
|
-
const normalizedAgent = agent
|
|
40696
|
+
const normalizedAgent = normalizeAgentName(agent);
|
|
40472
40697
|
const result = db2.prepare("DELETE FROM agent_presence WHERE LOWER(agent) = ?").run(normalizedAgent);
|
|
40473
40698
|
return result.changes > 0;
|
|
40474
40699
|
}
|
|
40475
40700
|
function renameAgent(oldName, newName) {
|
|
40476
40701
|
const db2 = getDb();
|
|
40477
|
-
const normalizedOld = oldName
|
|
40478
|
-
const normalizedNew = newName
|
|
40702
|
+
const normalizedOld = normalizeAgentName(oldName);
|
|
40703
|
+
const normalizedNew = normalizeAgentName(newName);
|
|
40479
40704
|
const existing = db2.prepare("SELECT agent FROM agent_presence WHERE LOWER(agent) = ?").get(normalizedOld);
|
|
40480
40705
|
if (!existing)
|
|
40481
40706
|
return false;
|
|
@@ -40485,17 +40710,53 @@ function renameAgent(oldName, newName) {
|
|
|
40485
40710
|
db2.prepare("UPDATE agent_presence SET agent = ? WHERE LOWER(agent) = ?").run(normalizedNew, normalizedOld);
|
|
40486
40711
|
return true;
|
|
40487
40712
|
}
|
|
40713
|
+
function setPresenceProject(agent, projectId) {
|
|
40714
|
+
const db2 = getDb();
|
|
40715
|
+
const normalizedAgent = normalizeAgentName(agent);
|
|
40716
|
+
const desiredProjectId = toStoredProjectId(projectId);
|
|
40717
|
+
const latest = getPresenceByAgent(db2, normalizedAgent);
|
|
40718
|
+
if (!latest) {
|
|
40719
|
+
heartbeat(normalizedAgent, "online", undefined, undefined, projectId);
|
|
40720
|
+
return;
|
|
40721
|
+
}
|
|
40722
|
+
const currentProjectId = toStoredProjectId(latest.project_id);
|
|
40723
|
+
if (currentProjectId === desiredProjectId)
|
|
40724
|
+
return;
|
|
40725
|
+
db2.transaction(() => {
|
|
40726
|
+
const latestId = latest.id;
|
|
40727
|
+
const target = getPresenceByAgentAndProject(db2, normalizedAgent, desiredProjectId);
|
|
40728
|
+
if (target && target.id !== latestId) {
|
|
40729
|
+
db2.prepare(`
|
|
40730
|
+
UPDATE agent_presence
|
|
40731
|
+
SET status = ?,
|
|
40732
|
+
last_seen_at = strftime('%Y-%m-%dT%H:%M:%f', 'now'),
|
|
40733
|
+
session_id = COALESCE(?, session_id),
|
|
40734
|
+
metadata = COALESCE(?, metadata)
|
|
40735
|
+
WHERE LOWER(agent) = ? AND COALESCE(project_id, '') = ?
|
|
40736
|
+
`).run(latest.status || target.status || "online", latest.session_id ?? null, latest.metadata ?? null, normalizedAgent, desiredProjectId);
|
|
40737
|
+
db2.prepare("DELETE FROM agent_presence WHERE id = ?").run(latestId);
|
|
40738
|
+
return;
|
|
40739
|
+
}
|
|
40740
|
+
db2.prepare(`
|
|
40741
|
+
UPDATE agent_presence
|
|
40742
|
+
SET project_id = ?, last_seen_at = strftime('%Y-%m-%dT%H:%M:%f', 'now')
|
|
40743
|
+
WHERE id = ?
|
|
40744
|
+
`).run(desiredProjectId, latestId);
|
|
40745
|
+
});
|
|
40746
|
+
}
|
|
40488
40747
|
|
|
40489
40748
|
// src/lib/messages.ts
|
|
40490
40749
|
init_db();
|
|
40491
40750
|
import { randomUUID } from "crypto";
|
|
40492
|
-
import { mkdirSync as mkdirSync6, copyFileSync as copyFileSync3, statSync as statSync2 } from "fs";
|
|
40493
|
-
import { join as join10 } from "path";
|
|
40751
|
+
import { mkdirSync as mkdirSync6, copyFileSync as copyFileSync3, statSync as statSync2, existsSync as existsSync9, realpathSync } from "fs";
|
|
40752
|
+
import { join as join10, basename, resolve } from "path";
|
|
40494
40753
|
|
|
40495
40754
|
// src/lib/webhooks.ts
|
|
40496
40755
|
init_db();
|
|
40497
40756
|
import { readFileSync as readFileSync3 } from "fs";
|
|
40498
40757
|
import { join as join9 } from "path";
|
|
40758
|
+
import dns from "dns";
|
|
40759
|
+
import net from "net";
|
|
40499
40760
|
var cachedConfig = null;
|
|
40500
40761
|
var configLoadedAt = 0;
|
|
40501
40762
|
var CONFIG_CACHE_MS = 1e4;
|
|
@@ -40530,6 +40791,44 @@ function matchesEvent(webhook, msg) {
|
|
|
40530
40791
|
}
|
|
40531
40792
|
return false;
|
|
40532
40793
|
}
|
|
40794
|
+
function isPrivateIP(ip) {
|
|
40795
|
+
if (net.isIPv4(ip)) {
|
|
40796
|
+
const parts = ip.split(".").map(Number);
|
|
40797
|
+
if (parts[0] === 10)
|
|
40798
|
+
return true;
|
|
40799
|
+
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
|
|
40800
|
+
return true;
|
|
40801
|
+
if (parts[0] === 192 && parts[1] === 168)
|
|
40802
|
+
return true;
|
|
40803
|
+
if (parts[0] === 127)
|
|
40804
|
+
return true;
|
|
40805
|
+
if (parts[0] === 169 && parts[1] === 254)
|
|
40806
|
+
return true;
|
|
40807
|
+
if (parts[0] === 0)
|
|
40808
|
+
return true;
|
|
40809
|
+
} else if (net.isIPv6(ip)) {
|
|
40810
|
+
const lower = ip.toLowerCase();
|
|
40811
|
+
if (lower === "::1" || lower.startsWith("::ffff:") || lower.startsWith("fc") || lower.startsWith("fd"))
|
|
40812
|
+
return true;
|
|
40813
|
+
}
|
|
40814
|
+
return false;
|
|
40815
|
+
}
|
|
40816
|
+
async function validateWebhookUrl(urlStr) {
|
|
40817
|
+
try {
|
|
40818
|
+
const url2 = new URL(urlStr);
|
|
40819
|
+
if (url2.protocol !== "https:")
|
|
40820
|
+
return false;
|
|
40821
|
+
const hostname4 = url2.hostname;
|
|
40822
|
+
if (hostname4 === "localhost" || hostname4 === "0.0.0.0" || hostname4 === "127.0.0.1" || hostname4 === "::1")
|
|
40823
|
+
return false;
|
|
40824
|
+
const addresses = await dns.promises.lookup(hostname4, { all: true });
|
|
40825
|
+
if (!Array.isArray(addresses))
|
|
40826
|
+
return false;
|
|
40827
|
+
return !addresses.some((a) => isPrivateIP(a.address));
|
|
40828
|
+
} catch {
|
|
40829
|
+
return false;
|
|
40830
|
+
}
|
|
40831
|
+
}
|
|
40533
40832
|
function fireWebhooks(msg) {
|
|
40534
40833
|
const config2 = loadConfig();
|
|
40535
40834
|
if (!config2.webhooks || config2.webhooks.length === 0)
|
|
@@ -40539,20 +40838,24 @@ function fireWebhooks(msg) {
|
|
|
40539
40838
|
continue;
|
|
40540
40839
|
if (!matchesEvent(webhook, msg))
|
|
40541
40840
|
continue;
|
|
40542
|
-
|
|
40543
|
-
|
|
40544
|
-
|
|
40545
|
-
|
|
40546
|
-
|
|
40547
|
-
|
|
40548
|
-
|
|
40549
|
-
|
|
40550
|
-
|
|
40551
|
-
|
|
40552
|
-
|
|
40553
|
-
|
|
40554
|
-
|
|
40555
|
-
|
|
40841
|
+
validateWebhookUrl(webhook.url).then((valid) => {
|
|
40842
|
+
if (!valid)
|
|
40843
|
+
return;
|
|
40844
|
+
fetch(webhook.url, {
|
|
40845
|
+
method: "POST",
|
|
40846
|
+
headers: { "Content-Type": "application/json" },
|
|
40847
|
+
body: JSON.stringify({
|
|
40848
|
+
id: msg.id,
|
|
40849
|
+
from: msg.from_agent,
|
|
40850
|
+
to: msg.to_agent,
|
|
40851
|
+
space: msg.space,
|
|
40852
|
+
content: msg.content,
|
|
40853
|
+
priority: msg.priority,
|
|
40854
|
+
blocking: msg.blocking,
|
|
40855
|
+
created_at: msg.created_at
|
|
40856
|
+
})
|
|
40857
|
+
}).catch(() => {});
|
|
40858
|
+
});
|
|
40556
40859
|
}
|
|
40557
40860
|
}
|
|
40558
40861
|
|
|
@@ -40597,6 +40900,22 @@ function getAttachmentsDir() {
|
|
|
40597
40900
|
return process.env.CONVERSATIONS_ATTACHMENTS_DIR;
|
|
40598
40901
|
return join10(getDataDir2(), "attachments");
|
|
40599
40902
|
}
|
|
40903
|
+
function validateAttachment(sourcePath, name) {
|
|
40904
|
+
const absolute = resolve(sourcePath);
|
|
40905
|
+
if (!existsSync9(absolute)) {
|
|
40906
|
+
throw new Error(`Attachment source not found: ${sourcePath}`);
|
|
40907
|
+
}
|
|
40908
|
+
const real = realpathSync(absolute);
|
|
40909
|
+
const stat = statSync2(real);
|
|
40910
|
+
if (!stat.isFile()) {
|
|
40911
|
+
throw new Error(`Attachment source must be a regular file: ${sourcePath}`);
|
|
40912
|
+
}
|
|
40913
|
+
const safeName = basename(name.replace(/\0/g, ""));
|
|
40914
|
+
if (!safeName || safeName.startsWith(".")) {
|
|
40915
|
+
throw new Error(`Invalid attachment name: ${name}`);
|
|
40916
|
+
}
|
|
40917
|
+
return { safeSource: real, safeName };
|
|
40918
|
+
}
|
|
40600
40919
|
function guessMimeType(name) {
|
|
40601
40920
|
const ext = name.split(".").pop()?.toLowerCase();
|
|
40602
40921
|
const mimeMap = {
|
|
@@ -40668,14 +40987,15 @@ function sendMessage(opts) {
|
|
|
40668
40987
|
mkdirSync6(attachmentsDir, { recursive: true });
|
|
40669
40988
|
const attachmentInfos = [];
|
|
40670
40989
|
for (const att of opts.attachments) {
|
|
40671
|
-
const
|
|
40672
|
-
|
|
40990
|
+
const { safeSource, safeName } = validateAttachment(att.source_path, att.name);
|
|
40991
|
+
const destPath = join10(attachmentsDir, safeName);
|
|
40992
|
+
copyFileSync3(safeSource, destPath);
|
|
40673
40993
|
const stat = statSync2(destPath);
|
|
40674
40994
|
attachmentInfos.push({
|
|
40675
|
-
name:
|
|
40995
|
+
name: safeName,
|
|
40676
40996
|
path: destPath,
|
|
40677
40997
|
size: stat.size,
|
|
40678
|
-
mime_type: guessMimeType(
|
|
40998
|
+
mime_type: guessMimeType(safeName)
|
|
40679
40999
|
});
|
|
40680
41000
|
}
|
|
40681
41001
|
const attachmentsJson = JSON.stringify(attachmentInfos);
|
|
@@ -40737,8 +41057,10 @@ function readMessages(opts = {}) {
|
|
|
40737
41057
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
40738
41058
|
const resolvedLimit = isLatest ? Math.floor(opts.latest) : Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
|
|
40739
41059
|
const order = isLatest ? "DESC" : opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
|
|
40740
|
-
const resolvedOffset =
|
|
40741
|
-
const
|
|
41060
|
+
const resolvedOffset = Number.isFinite(opts.offset) ? Math.floor(opts.offset) : 0;
|
|
41061
|
+
const safeLimit = Math.max(1, Math.min(resolvedLimit, 1e4));
|
|
41062
|
+
const safeOffset = Math.max(0, Math.floor(resolvedOffset));
|
|
41063
|
+
const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} LIMIT ${safeLimit} OFFSET ${safeOffset}`).all(...params);
|
|
40742
41064
|
let messages = rows.map(parseMessage);
|
|
40743
41065
|
if (opts.include_reply_counts && messages.length > 0) {
|
|
40744
41066
|
const db22 = getDb();
|
|
@@ -40778,10 +41100,20 @@ function getMessageById(id) {
|
|
|
40778
41100
|
const row = db2.prepare("SELECT * FROM messages WHERE id = ?").get(id);
|
|
40779
41101
|
return row ? parseMessage(row) : null;
|
|
40780
41102
|
}
|
|
40781
|
-
function markReadByIds(ids) {
|
|
41103
|
+
function markReadByIds(ids, agent) {
|
|
40782
41104
|
const db2 = getDb();
|
|
40783
41105
|
if (ids.length === 0)
|
|
40784
41106
|
return 0;
|
|
41107
|
+
if (agent) {
|
|
41108
|
+
const stmt2 = db2.prepare(`INSERT OR REPLACE INTO message_read_receipts (message_id, agent, read_at)
|
|
41109
|
+
VALUES (?, ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'))`);
|
|
41110
|
+
const normalized = agent.toLowerCase();
|
|
41111
|
+
for (const id of ids)
|
|
41112
|
+
stmt2.run(id, normalized);
|
|
41113
|
+
const placeholders2 = ids.map(() => "?").join(", ");
|
|
41114
|
+
const update = db2.prepare(`UPDATE messages SET read_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE id IN (${placeholders2}) AND read_at IS NULL`);
|
|
41115
|
+
return update.run(...ids).changes;
|
|
41116
|
+
}
|
|
40785
41117
|
const placeholders = ids.map(() => "?").join(", ");
|
|
40786
41118
|
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`);
|
|
40787
41119
|
const result = stmt.run(...ids);
|
|
@@ -40929,8 +41261,9 @@ function getPinnedMessages(opts) {
|
|
|
40929
41261
|
params.push(opts.session_id);
|
|
40930
41262
|
}
|
|
40931
41263
|
const where = `WHERE ${conditions.join(" AND ")}`;
|
|
40932
|
-
const
|
|
40933
|
-
const
|
|
41264
|
+
const safeLimit = Number.isFinite(opts?.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 0;
|
|
41265
|
+
const limitClause = safeLimit > 0 ? `LIMIT ${safeLimit}` : "";
|
|
41266
|
+
const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY pinned_at DESC, id DESC ${limitClause}`).all(...params);
|
|
40934
41267
|
return rows.map(parseMessage);
|
|
40935
41268
|
}
|
|
40936
41269
|
function getUnreadBlockers(agent) {
|
|
@@ -41119,11 +41452,11 @@ function getMessagesForAgent(agent, opts) {
|
|
|
41119
41452
|
if (opts?.unread_only) {
|
|
41120
41453
|
conditions.push("mm.notified_at IS NULL");
|
|
41121
41454
|
}
|
|
41122
|
-
const
|
|
41455
|
+
const safeLimit = Math.max(1, Math.min(Math.floor(opts?.limit ?? 50), 1000));
|
|
41123
41456
|
const rows = db2.prepare(`SELECT m.*, mm.id AS mention_id FROM messages m
|
|
41124
41457
|
JOIN message_mentions mm ON mm.message_id = m.id
|
|
41125
41458
|
WHERE ${conditions.join(" AND ")}
|
|
41126
|
-
ORDER BY m.created_at DESC LIMIT ${
|
|
41459
|
+
ORDER BY m.created_at DESC LIMIT ${safeLimit}`).all(...params);
|
|
41127
41460
|
return rows.map(({ mention_id, ...row }) => ({ message: parseMessage(row), mention_id }));
|
|
41128
41461
|
}
|
|
41129
41462
|
function markMentionsRead(agent, space) {
|
|
@@ -41231,6 +41564,7 @@ function getSessionActivity(sessionId) {
|
|
|
41231
41564
|
// src/mcp/tools/messaging.ts
|
|
41232
41565
|
init_identity();
|
|
41233
41566
|
function registerMessagingTools(server, resolveProjectId) {
|
|
41567
|
+
const _sessionInjectRate = new Map;
|
|
41234
41568
|
server.registerTool("send_message", {
|
|
41235
41569
|
description: "Send a DM to an agent by name, or to a specific agent-claude session by ID. When target_session_id is provided, the message is routed to that exact session and auto-injected into its conversation.",
|
|
41236
41570
|
inputSchema: {
|
|
@@ -41270,6 +41604,22 @@ function registerMessagingTools(server, resolveProjectId) {
|
|
|
41270
41604
|
}, async (args) => {
|
|
41271
41605
|
const { target_session_id, content, from: fromParam, priority } = args;
|
|
41272
41606
|
const from = resolveIdentity(fromParam);
|
|
41607
|
+
const rateKey = `session:${from}:${target_session_id}`;
|
|
41608
|
+
const now = Date.now();
|
|
41609
|
+
const windowMs = 60000;
|
|
41610
|
+
const maxPerWindow = 10;
|
|
41611
|
+
const rateEntry = _sessionInjectRate.get(rateKey);
|
|
41612
|
+
if (rateEntry && now - rateEntry.start < windowMs && rateEntry.count >= maxPerWindow) {
|
|
41613
|
+
return {
|
|
41614
|
+
content: [{ type: "text", text: `Rate limit: max ${maxPerWindow} session injections per minute. Try again soon.` }],
|
|
41615
|
+
isError: true
|
|
41616
|
+
};
|
|
41617
|
+
}
|
|
41618
|
+
if (!rateEntry || now - rateEntry.start >= windowMs) {
|
|
41619
|
+
_sessionInjectRate.set(rateKey, { count: 1, start: now });
|
|
41620
|
+
} else {
|
|
41621
|
+
rateEntry.count++;
|
|
41622
|
+
}
|
|
41273
41623
|
const msg = sendMessage({
|
|
41274
41624
|
from,
|
|
41275
41625
|
to: `session:${target_session_id}`,
|
|
@@ -41307,7 +41657,7 @@ function registerMessagingTools(server, resolveProjectId) {
|
|
|
41307
41657
|
project_id: args.project_id ?? resolveProjectId(undefined, agent)
|
|
41308
41658
|
});
|
|
41309
41659
|
if (args.mark_read !== false && messages.length > 0) {
|
|
41310
|
-
markReadByIds(messages.map((m) => m.id));
|
|
41660
|
+
markReadByIds(messages.map((m) => m.id), agent);
|
|
41311
41661
|
}
|
|
41312
41662
|
return {
|
|
41313
41663
|
content: [{ type: "text", text: JSON.stringify({ messages, count: messages.length, offset: args.offset ?? 0 }) }]
|
|
@@ -42691,8 +43041,7 @@ function registerAgentTools(server, agentFocus, getAgentFocus) {
|
|
|
42691
43041
|
const { project_id, from: fromParam } = args;
|
|
42692
43042
|
const agent = resolveIdentity(fromParam);
|
|
42693
43043
|
agentFocus.set(agent, { project_id });
|
|
42694
|
-
|
|
42695
|
-
db2.prepare("UPDATE agent_presence SET project_id = ? WHERE agent = ?").run(project_id, agent);
|
|
43044
|
+
setPresenceProject(agent, project_id);
|
|
42696
43045
|
return {
|
|
42697
43046
|
content: [{ type: "text", text: JSON.stringify({ agent, focused: true, project_id }) }]
|
|
42698
43047
|
};
|
|
@@ -42727,8 +43076,7 @@ function registerAgentTools(server, agentFocus, getAgentFocus) {
|
|
|
42727
43076
|
}, async (args) => {
|
|
42728
43077
|
const agent = resolveIdentity(args.from);
|
|
42729
43078
|
agentFocus.delete(agent);
|
|
42730
|
-
|
|
42731
|
-
db2.prepare("UPDATE agent_presence SET project_id = NULL WHERE agent = ?").run(agent);
|
|
43079
|
+
setPresenceProject(agent, null);
|
|
42732
43080
|
return {
|
|
42733
43081
|
content: [{ type: "text", text: JSON.stringify({ agent, focused: false, project_id: null }) }]
|
|
42734
43082
|
};
|
|
@@ -44315,7 +44663,7 @@ ${msg.text}`;
|
|
|
44315
44663
|
// src/cli/commands/tmux.ts
|
|
44316
44664
|
import { execSync } from "child_process";
|
|
44317
44665
|
function sleep(ms) {
|
|
44318
|
-
return new Promise((
|
|
44666
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
44319
44667
|
}
|
|
44320
44668
|
function countLines(message) {
|
|
44321
44669
|
const matches = message.match(/\r?\n/g);
|
|
@@ -44359,7 +44707,7 @@ async function tmuxSend(target, message, opts = {}) {
|
|
|
44359
44707
|
|
|
44360
44708
|
// src/mcp/tools/tmux.ts
|
|
44361
44709
|
function sleep2(ms) {
|
|
44362
|
-
return new Promise((
|
|
44710
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
44363
44711
|
}
|
|
44364
44712
|
function parseOptionalDelayMs(value) {
|
|
44365
44713
|
return typeof value === "number" && value >= 0 ? value : undefined;
|
|
@@ -44460,7 +44808,7 @@ function registerTmuxTools(server) {
|
|
|
44460
44808
|
// package.json
|
|
44461
44809
|
var package_default = {
|
|
44462
44810
|
name: "@hasna/conversations",
|
|
44463
|
-
version: "0.2.
|
|
44811
|
+
version: "0.2.44",
|
|
44464
44812
|
description: "Real-time CLI messaging for AI agents",
|
|
44465
44813
|
type: "module",
|
|
44466
44814
|
bin: {
|