@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/dist/index.js CHANGED
@@ -5,44 +5,65 @@ var __defProp = Object.defineProperty;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
8
13
  var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
- var __moduleCache = /* @__PURE__ */ new WeakMap;
20
33
  var __toCommonJS = (from) => {
21
- var entry = __moduleCache.get(from), desc;
34
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
22
35
  if (entry)
23
36
  return entry;
24
37
  entry = __defProp({}, "__esModule", { value: true });
25
- if (from && typeof from === "object" || typeof from === "function")
26
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
27
- get: () => from[key],
28
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
29
- }));
38
+ if (from && typeof from === "object" || typeof from === "function") {
39
+ for (var key of __getOwnPropNames(from))
40
+ if (!__hasOwnProp.call(entry, key))
41
+ __defProp(entry, key, {
42
+ get: __accessProp.bind(from, key),
43
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
44
+ });
45
+ }
30
46
  __moduleCache.set(from, entry);
31
47
  return entry;
32
48
  };
49
+ var __moduleCache;
33
50
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
51
+ var __returnValue = (v) => v;
52
+ function __exportSetter(name, newValue) {
53
+ this[name] = __returnValue.bind(null, newValue);
54
+ }
34
55
  var __export = (target, all) => {
35
56
  for (var name in all)
36
57
  __defProp(target, name, {
37
58
  get: all[name],
38
59
  enumerable: true,
39
60
  configurable: true,
40
- set: (newValue) => all[name] = () => newValue
61
+ set: __exportSetter.bind(all, name)
41
62
  });
42
63
  };
43
64
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
44
65
 
45
- // ../../../../node_modules/@hasna/cloud/dist/index.js
66
+ // node_modules/@hasna/cloud/dist/index.js
46
67
  import { createRequire } from "module";
47
68
  import { Database } from "bun:sqlite";
48
69
  import {
@@ -63,11 +84,11 @@ import { homedir as homedir4 } from "os";
63
84
  import { join as join4 } from "path";
64
85
  import { join as join6, dirname } from "path";
65
86
  import { homedir as homedir5, platform } from "os";
66
- function __accessProp(key) {
87
+ function __accessProp2(key) {
67
88
  return this[key];
68
89
  }
69
- function __exportSetter(name, newValue) {
70
- this[name] = __returnValue.bind(null, newValue);
90
+ function __exportSetter2(name, newValue) {
91
+ this[name] = __returnValue2.bind(null, newValue);
71
92
  }
72
93
  function translateSql(sql, dialect) {
73
94
  if (dialect === "sqlite")
@@ -1097,10 +1118,10 @@ class SyncProgressTracker {
1097
1118
  }
1098
1119
  }
1099
1120
  }
1100
- var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __toESMCache_node, __toESMCache_esm, __toESM2 = (mod, isNodeMode, target) => {
1121
+ var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __toESMCache_node2, __toESMCache_esm2, __toESM2 = (mod, isNodeMode, target) => {
1101
1122
  var canCache = mod != null && typeof mod === "object";
1102
1123
  if (canCache) {
1103
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
1124
+ var cache = isNodeMode ? __toESMCache_node2 ??= new WeakMap : __toESMCache_esm2 ??= new WeakMap;
1104
1125
  var cached = cache.get(mod);
1105
1126
  if (cached)
1106
1127
  return cached;
@@ -1110,19 +1131,19 @@ var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __t
1110
1131
  for (let key of __getOwnPropNames2(mod))
1111
1132
  if (!__hasOwnProp2.call(to, key))
1112
1133
  __defProp2(to, key, {
1113
- get: __accessProp.bind(mod, key),
1134
+ get: __accessProp2.bind(mod, key),
1114
1135
  enumerable: true
1115
1136
  });
1116
1137
  if (canCache)
1117
1138
  cache.set(mod, to);
1118
1139
  return to;
1119
- }, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __returnValue = (v) => v, __export2 = (target, all) => {
1140
+ }, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __returnValue2 = (v) => v, __export2 = (target, all) => {
1120
1141
  for (var name in all)
1121
1142
  __defProp2(target, name, {
1122
1143
  get: all[name],
1123
1144
  enumerable: true,
1124
1145
  configurable: true,
1125
- set: __exportSetter.bind(all, name)
1146
+ set: __exportSetter2.bind(all, name)
1126
1147
  });
1127
1148
  }, __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_defaults, require_utils, require_utils_legacy, require_utils_webcrypto, require_utils2, 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_dist, 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, util, objectUtil, ZodParsedType, getParsedType = (data) => {
1128
1149
  const t = typeof data;
@@ -9459,6 +9480,137 @@ function getDbPath2() {
9459
9480
  return process.env.CONVERSATIONS_DB_PATH;
9460
9481
  return join5(getDataDir2(), "messages.db");
9461
9482
  }
9483
+ function parsePresenceTimestamp(value) {
9484
+ if (typeof value !== "string" || !value)
9485
+ return 0;
9486
+ return new Date(`${value}Z`).getTime() || 0;
9487
+ }
9488
+ function normalizePresenceText(value) {
9489
+ if (typeof value !== "string")
9490
+ return null;
9491
+ const normalized = value.trim();
9492
+ return normalized ? normalized : null;
9493
+ }
9494
+ function shouldRebuildAgentPresenceTable(columns) {
9495
+ const byName = new Map(columns.map((column) => [column.name, column]));
9496
+ const agentCol = byName.get("agent");
9497
+ const projectCol = byName.get("project_id");
9498
+ if (!agentCol)
9499
+ return false;
9500
+ if (!projectCol)
9501
+ return true;
9502
+ return agentCol.pk !== 1 || projectCol.pk !== 2 || projectCol.notnull !== 1 || byName.has("pid");
9503
+ }
9504
+ function rebuildLegacyAgentPresenceTable(db2) {
9505
+ const fallbackNow = db2.prepare("SELECT strftime('%Y-%m-%dT%H:%M:%f', 'now') AS now").get().now;
9506
+ const legacyRows = db2.prepare("SELECT rowid AS _rowid, * FROM agent_presence").all();
9507
+ legacyRows.sort((left, right) => {
9508
+ const lastSeenDelta = parsePresenceTimestamp(right.last_seen_at) - parsePresenceTimestamp(left.last_seen_at);
9509
+ if (lastSeenDelta !== 0)
9510
+ return lastSeenDelta;
9511
+ const createdDelta = parsePresenceTimestamp(right.created_at) - parsePresenceTimestamp(left.created_at);
9512
+ if (createdDelta !== 0)
9513
+ return createdDelta;
9514
+ const projectDelta = Number(Boolean(normalizePresenceText(right.project_id))) - Number(Boolean(normalizePresenceText(left.project_id)));
9515
+ if (projectDelta !== 0)
9516
+ return projectDelta;
9517
+ return right._rowid - left._rowid;
9518
+ });
9519
+ const dedupedRows = new Map;
9520
+ for (const row of legacyRows) {
9521
+ const normalizedAgent = normalizePresenceText(row.agent)?.toLowerCase();
9522
+ if (!normalizedAgent)
9523
+ continue;
9524
+ const storedProjectId = normalizePresenceText(row.project_id) ?? "";
9525
+ const dedupeKey = `${normalizedAgent}\x00${storedProjectId}`;
9526
+ if (dedupedRows.has(dedupeKey))
9527
+ continue;
9528
+ dedupedRows.set(dedupeKey, row);
9529
+ }
9530
+ db2.exec("BEGIN");
9531
+ try {
9532
+ db2.exec(`
9533
+ CREATE TABLE agent_presence_new (
9534
+ id TEXT NOT NULL,
9535
+ agent TEXT NOT NULL,
9536
+ session_id TEXT,
9537
+ role TEXT NOT NULL DEFAULT 'agent',
9538
+ project_id TEXT NOT NULL DEFAULT '',
9539
+ status TEXT NOT NULL DEFAULT 'online',
9540
+ last_seen_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
9541
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
9542
+ metadata TEXT,
9543
+ PRIMARY KEY (agent, project_id)
9544
+ )
9545
+ `);
9546
+ const insertPresence = db2.prepare(`
9547
+ INSERT INTO agent_presence_new (id, agent, session_id, role, project_id, status, last_seen_at, created_at, metadata)
9548
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
9549
+ `);
9550
+ for (const [dedupeKey, row] of dedupedRows) {
9551
+ const [agent, projectKey] = dedupeKey.split("\x00");
9552
+ const id = normalizePresenceText(row.id) ?? crypto.randomUUID().slice(0, 8);
9553
+ const sessionId = normalizePresenceText(row.session_id);
9554
+ const role = normalizePresenceText(row.role) ?? "agent";
9555
+ const projectId = projectKey;
9556
+ const status = normalizePresenceText(row.status) ?? "online";
9557
+ const lastSeenAt = normalizePresenceText(row.last_seen_at) ?? fallbackNow;
9558
+ const createdAt = normalizePresenceText(row.created_at) ?? lastSeenAt;
9559
+ const metadata = typeof row.metadata === "string" ? row.metadata : row.metadata == null ? null : JSON.stringify(row.metadata);
9560
+ insertPresence.run(id, agent, sessionId, role, projectId, status, lastSeenAt, createdAt, metadata);
9561
+ }
9562
+ db2.exec("DROP TABLE agent_presence");
9563
+ db2.exec("ALTER TABLE agent_presence_new RENAME TO agent_presence");
9564
+ db2.exec("COMMIT");
9565
+ } catch (error) {
9566
+ db2.exec("ROLLBACK");
9567
+ throw error;
9568
+ }
9569
+ }
9570
+ function collapseDuplicateAgentPresenceRows(db2) {
9571
+ const rows = db2.prepare("SELECT rowid AS _rowid, * FROM agent_presence").all();
9572
+ rows.sort((left, right) => {
9573
+ const lastSeenDelta = parsePresenceTimestamp(right.last_seen_at) - parsePresenceTimestamp(left.last_seen_at);
9574
+ if (lastSeenDelta !== 0)
9575
+ return lastSeenDelta;
9576
+ const createdDelta = parsePresenceTimestamp(right.created_at) - parsePresenceTimestamp(left.created_at);
9577
+ if (createdDelta !== 0)
9578
+ return createdDelta;
9579
+ const projectDelta = Number(Boolean(normalizePresenceText(right.project_id))) - Number(Boolean(normalizePresenceText(left.project_id)));
9580
+ if (projectDelta !== 0)
9581
+ return projectDelta;
9582
+ return right._rowid - left._rowid;
9583
+ });
9584
+ const rowIdsToDelete = [];
9585
+ const seenAgents = new Set;
9586
+ for (const row of rows) {
9587
+ const normalizedAgent = normalizePresenceText(row.agent)?.toLowerCase();
9588
+ if (!normalizedAgent)
9589
+ continue;
9590
+ if (seenAgents.has(normalizedAgent)) {
9591
+ rowIdsToDelete.push(row._rowid);
9592
+ continue;
9593
+ }
9594
+ seenAgents.add(normalizedAgent);
9595
+ }
9596
+ if (rowIdsToDelete.length === 0)
9597
+ return;
9598
+ db2.exec("BEGIN");
9599
+ try {
9600
+ const deleteRow = db2.prepare("DELETE FROM agent_presence WHERE rowid = ?");
9601
+ for (const rowId of rowIdsToDelete) {
9602
+ deleteRow.run(rowId);
9603
+ }
9604
+ db2.exec("COMMIT");
9605
+ } catch (error) {
9606
+ db2.exec("ROLLBACK");
9607
+ throw error;
9608
+ }
9609
+ }
9610
+ function ensureAgentPresenceAgentUniqueIndex(db2) {
9611
+ collapseDuplicateAgentPresenceRows(db2);
9612
+ db2.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_presence_agent_unique ON agent_presence(agent)");
9613
+ }
9462
9614
  function getDb() {
9463
9615
  if (db)
9464
9616
  return db;
@@ -9531,16 +9683,18 @@ function getDb() {
9531
9683
  db.exec(`
9532
9684
  CREATE TABLE IF NOT EXISTS agent_presence (
9533
9685
  id TEXT NOT NULL,
9534
- agent TEXT PRIMARY KEY,
9686
+ agent TEXT NOT NULL,
9535
9687
  session_id TEXT,
9536
9688
  role TEXT NOT NULL DEFAULT 'agent',
9537
- project_id TEXT,
9689
+ project_id TEXT NOT NULL DEFAULT '',
9538
9690
  status TEXT NOT NULL DEFAULT 'online',
9539
9691
  last_seen_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
9540
9692
  created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
9541
- metadata TEXT
9693
+ metadata TEXT,
9694
+ PRIMARY KEY (agent, project_id)
9542
9695
  )
9543
9696
  `);
9697
+ ensureAgentPresenceAgentUniqueIndex(db);
9544
9698
  db.exec(`
9545
9699
  CREATE TABLE IF NOT EXISTS resource_locks (
9546
9700
  resource_type TEXT NOT NULL,
@@ -9642,8 +9796,13 @@ function getDb() {
9642
9796
  db.exec("UPDATE messages SET uuid = lower(hex(randomblob(16))) WHERE uuid IS NULL");
9643
9797
  db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_uuid ON messages(uuid)");
9644
9798
  }
9645
- const presenceCols = db.prepare("PRAGMA table_info(agent_presence)").all();
9646
- const presenceColNames = presenceCols.map((c) => c.name);
9799
+ let presenceCols = db.prepare("PRAGMA table_info(agent_presence)").all();
9800
+ let presenceColNames = presenceCols.map((c) => c.name);
9801
+ if (shouldRebuildAgentPresenceTable(presenceCols)) {
9802
+ rebuildLegacyAgentPresenceTable(db);
9803
+ presenceCols = db.prepare("PRAGMA table_info(agent_presence)").all();
9804
+ presenceColNames = presenceCols.map((c) => c.name);
9805
+ }
9647
9806
  if (!presenceColNames.includes("id")) {
9648
9807
  db.exec("ALTER TABLE agent_presence ADD COLUMN id TEXT NOT NULL DEFAULT ''");
9649
9808
  const rows = db.prepare("SELECT agent FROM agent_presence").all();
@@ -9664,7 +9823,9 @@ function getDb() {
9664
9823
  }
9665
9824
  if (!presenceColNames.includes("project_id")) {
9666
9825
  db.exec("ALTER TABLE agent_presence ADD COLUMN project_id TEXT");
9826
+ db.exec("UPDATE agent_presence SET project_id = '' WHERE project_id IS NULL");
9667
9827
  }
9828
+ ensureAgentPresenceAgentUniqueIndex(db);
9668
9829
  db.exec(`
9669
9830
  CREATE TABLE IF NOT EXISTS message_read_receipts (
9670
9831
  message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
@@ -11386,14 +11547,14 @@ Check the top-level render call using <` + parentName + ">.";
11386
11547
  var thenableResult = result;
11387
11548
  var wasAwaited = false;
11388
11549
  var thenable = {
11389
- then: function(resolve, reject) {
11550
+ then: function(resolve2, reject) {
11390
11551
  wasAwaited = true;
11391
11552
  thenableResult.then(function(returnValue2) {
11392
11553
  popActScope(prevActScopeDepth);
11393
11554
  if (actScopeDepth === 0) {
11394
- recursivelyFlushAsyncActWork(returnValue2, resolve, reject);
11555
+ recursivelyFlushAsyncActWork(returnValue2, resolve2, reject);
11395
11556
  } else {
11396
- resolve(returnValue2);
11557
+ resolve2(returnValue2);
11397
11558
  }
11398
11559
  }, function(error2) {
11399
11560
  popActScope(prevActScopeDepth);
@@ -11422,20 +11583,20 @@ Check the top-level render call using <` + parentName + ">.";
11422
11583
  ReactCurrentActQueue.current = null;
11423
11584
  }
11424
11585
  var _thenable = {
11425
- then: function(resolve, reject) {
11586
+ then: function(resolve2, reject) {
11426
11587
  if (ReactCurrentActQueue.current === null) {
11427
11588
  ReactCurrentActQueue.current = [];
11428
- recursivelyFlushAsyncActWork(returnValue, resolve, reject);
11589
+ recursivelyFlushAsyncActWork(returnValue, resolve2, reject);
11429
11590
  } else {
11430
- resolve(returnValue);
11591
+ resolve2(returnValue);
11431
11592
  }
11432
11593
  }
11433
11594
  };
11434
11595
  return _thenable;
11435
11596
  } else {
11436
11597
  var _thenable2 = {
11437
- then: function(resolve, reject) {
11438
- resolve(returnValue);
11598
+ then: function(resolve2, reject) {
11599
+ resolve2(returnValue);
11439
11600
  }
11440
11601
  };
11441
11602
  return _thenable2;
@@ -11451,7 +11612,7 @@ Check the top-level render call using <` + parentName + ">.";
11451
11612
  actScopeDepth = prevActScopeDepth;
11452
11613
  }
11453
11614
  }
11454
- function recursivelyFlushAsyncActWork(returnValue, resolve, reject) {
11615
+ function recursivelyFlushAsyncActWork(returnValue, resolve2, reject) {
11455
11616
  {
11456
11617
  var queue = ReactCurrentActQueue.current;
11457
11618
  if (queue !== null) {
@@ -11460,16 +11621,16 @@ Check the top-level render call using <` + parentName + ">.";
11460
11621
  enqueueTask(function() {
11461
11622
  if (queue.length === 0) {
11462
11623
  ReactCurrentActQueue.current = null;
11463
- resolve(returnValue);
11624
+ resolve2(returnValue);
11464
11625
  } else {
11465
- recursivelyFlushAsyncActWork(returnValue, resolve, reject);
11626
+ recursivelyFlushAsyncActWork(returnValue, resolve2, reject);
11466
11627
  }
11467
11628
  });
11468
11629
  } catch (error2) {
11469
11630
  reject(error2);
11470
11631
  }
11471
11632
  } else {
11472
- resolve(returnValue);
11633
+ resolve2(returnValue);
11473
11634
  }
11474
11635
  }
11475
11636
  }
@@ -11560,13 +11721,15 @@ var require_react = __commonJS((exports, module) => {
11560
11721
  // src/lib/messages.ts
11561
11722
  init_db();
11562
11723
  import { randomUUID } from "crypto";
11563
- import { mkdirSync as mkdirSync5, copyFileSync as copyFileSync3, statSync as statSync2 } from "fs";
11564
- import { join as join8 } from "path";
11724
+ import { mkdirSync as mkdirSync5, copyFileSync as copyFileSync3, statSync as statSync2, existsSync as existsSync6, realpathSync } from "fs";
11725
+ import { join as join8, basename, resolve } from "path";
11565
11726
 
11566
11727
  // src/lib/webhooks.ts
11567
11728
  init_db();
11568
11729
  import { readFileSync as readFileSync2 } from "fs";
11569
11730
  import { join as join7 } from "path";
11731
+ import dns from "dns";
11732
+ import net from "net";
11570
11733
  var cachedConfig = null;
11571
11734
  var configLoadedAt = 0;
11572
11735
  var CONFIG_CACHE_MS = 1e4;
@@ -11601,6 +11764,44 @@ function matchesEvent(webhook, msg) {
11601
11764
  }
11602
11765
  return false;
11603
11766
  }
11767
+ function isPrivateIP(ip) {
11768
+ if (net.isIPv4(ip)) {
11769
+ const parts = ip.split(".").map(Number);
11770
+ if (parts[0] === 10)
11771
+ return true;
11772
+ if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
11773
+ return true;
11774
+ if (parts[0] === 192 && parts[1] === 168)
11775
+ return true;
11776
+ if (parts[0] === 127)
11777
+ return true;
11778
+ if (parts[0] === 169 && parts[1] === 254)
11779
+ return true;
11780
+ if (parts[0] === 0)
11781
+ return true;
11782
+ } else if (net.isIPv6(ip)) {
11783
+ const lower = ip.toLowerCase();
11784
+ if (lower === "::1" || lower.startsWith("::ffff:") || lower.startsWith("fc") || lower.startsWith("fd"))
11785
+ return true;
11786
+ }
11787
+ return false;
11788
+ }
11789
+ async function validateWebhookUrl(urlStr) {
11790
+ try {
11791
+ const url = new URL(urlStr);
11792
+ if (url.protocol !== "https:")
11793
+ return false;
11794
+ const hostname = url.hostname;
11795
+ if (hostname === "localhost" || hostname === "0.0.0.0" || hostname === "127.0.0.1" || hostname === "::1")
11796
+ return false;
11797
+ const addresses = await dns.promises.lookup(hostname, { all: true });
11798
+ if (!Array.isArray(addresses))
11799
+ return false;
11800
+ return !addresses.some((a) => isPrivateIP(a.address));
11801
+ } catch {
11802
+ return false;
11803
+ }
11804
+ }
11604
11805
  function fireWebhooks(msg) {
11605
11806
  const config = loadConfig();
11606
11807
  if (!config.webhooks || config.webhooks.length === 0)
@@ -11610,20 +11811,24 @@ function fireWebhooks(msg) {
11610
11811
  continue;
11611
11812
  if (!matchesEvent(webhook, msg))
11612
11813
  continue;
11613
- fetch(webhook.url, {
11614
- method: "POST",
11615
- headers: { "Content-Type": "application/json" },
11616
- body: JSON.stringify({
11617
- id: msg.id,
11618
- from: msg.from_agent,
11619
- to: msg.to_agent,
11620
- space: msg.space,
11621
- content: msg.content,
11622
- priority: msg.priority,
11623
- blocking: msg.blocking,
11624
- created_at: msg.created_at
11625
- })
11626
- }).catch(() => {});
11814
+ validateWebhookUrl(webhook.url).then((valid) => {
11815
+ if (!valid)
11816
+ return;
11817
+ fetch(webhook.url, {
11818
+ method: "POST",
11819
+ headers: { "Content-Type": "application/json" },
11820
+ body: JSON.stringify({
11821
+ id: msg.id,
11822
+ from: msg.from_agent,
11823
+ to: msg.to_agent,
11824
+ space: msg.space,
11825
+ content: msg.content,
11826
+ priority: msg.priority,
11827
+ blocking: msg.blocking,
11828
+ created_at: msg.created_at
11829
+ })
11830
+ }).catch(() => {});
11831
+ });
11627
11832
  }
11628
11833
  }
11629
11834
 
@@ -11668,6 +11873,22 @@ function getAttachmentsDir() {
11668
11873
  return process.env.CONVERSATIONS_ATTACHMENTS_DIR;
11669
11874
  return join8(getDataDir2(), "attachments");
11670
11875
  }
11876
+ function validateAttachment(sourcePath, name) {
11877
+ const absolute = resolve(sourcePath);
11878
+ if (!existsSync6(absolute)) {
11879
+ throw new Error(`Attachment source not found: ${sourcePath}`);
11880
+ }
11881
+ const real = realpathSync(absolute);
11882
+ const stat = statSync2(real);
11883
+ if (!stat.isFile()) {
11884
+ throw new Error(`Attachment source must be a regular file: ${sourcePath}`);
11885
+ }
11886
+ const safeName = basename(name.replace(/\0/g, ""));
11887
+ if (!safeName || safeName.startsWith(".")) {
11888
+ throw new Error(`Invalid attachment name: ${name}`);
11889
+ }
11890
+ return { safeSource: real, safeName };
11891
+ }
11671
11892
  function guessMimeType(name) {
11672
11893
  const ext = name.split(".").pop()?.toLowerCase();
11673
11894
  const mimeMap = {
@@ -11739,14 +11960,15 @@ function sendMessage(opts) {
11739
11960
  mkdirSync5(attachmentsDir, { recursive: true });
11740
11961
  const attachmentInfos = [];
11741
11962
  for (const att of opts.attachments) {
11742
- const destPath = join8(attachmentsDir, att.name);
11743
- copyFileSync3(att.source_path, destPath);
11963
+ const { safeSource, safeName } = validateAttachment(att.source_path, att.name);
11964
+ const destPath = join8(attachmentsDir, safeName);
11965
+ copyFileSync3(safeSource, destPath);
11744
11966
  const stat = statSync2(destPath);
11745
11967
  attachmentInfos.push({
11746
- name: att.name,
11968
+ name: safeName,
11747
11969
  path: destPath,
11748
11970
  size: stat.size,
11749
- mime_type: guessMimeType(att.name)
11971
+ mime_type: guessMimeType(safeName)
11750
11972
  });
11751
11973
  }
11752
11974
  const attachmentsJson = JSON.stringify(attachmentInfos);
@@ -11808,8 +12030,10 @@ function readMessages(opts = {}) {
11808
12030
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
11809
12031
  const resolvedLimit = isLatest ? Math.floor(opts.latest) : Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
11810
12032
  const order = isLatest ? "DESC" : opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
11811
- const resolvedOffset = opts.offset && opts.offset > 0 ? Math.floor(opts.offset) : 0;
11812
- const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} LIMIT ${resolvedLimit} OFFSET ${resolvedOffset}`).all(...params);
12033
+ const resolvedOffset = Number.isFinite(opts.offset) ? Math.floor(opts.offset) : 0;
12034
+ const safeLimit = Math.max(1, Math.min(resolvedLimit, 1e4));
12035
+ const safeOffset = Math.max(0, Math.floor(resolvedOffset));
12036
+ const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} LIMIT ${safeLimit} OFFSET ${safeOffset}`).all(...params);
11813
12037
  let messages = rows.map(parseMessage);
11814
12038
  if (opts.include_reply_counts && messages.length > 0) {
11815
12039
  const db22 = getDb();
@@ -11954,8 +12178,9 @@ function getPinnedMessages(opts) {
11954
12178
  params.push(opts.session_id);
11955
12179
  }
11956
12180
  const where = `WHERE ${conditions.join(" AND ")}`;
11957
- const limit = Number.isFinite(opts?.limit) && opts.limit > 0 ? `LIMIT ${Math.floor(opts.limit)}` : "";
11958
- const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY pinned_at DESC, id DESC ${limit}`).all(...params);
12181
+ const safeLimit = Number.isFinite(opts?.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 0;
12182
+ const limitClause = safeLimit > 0 ? `LIMIT ${safeLimit}` : "";
12183
+ const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY pinned_at DESC, id DESC ${limitClause}`).all(...params);
11959
12184
  return rows.map(parseMessage);
11960
12185
  }
11961
12186
  function getUnreadBlockers(agent) {
@@ -13064,6 +13289,33 @@ function getReactionSummary(messageId) {
13064
13289
  init_db();
13065
13290
  var ONLINE_THRESHOLD_SECONDS = 60;
13066
13291
  var CONFLICT_THRESHOLD_SECONDS = 30 * 60;
13292
+ function normalizeAgentName(name) {
13293
+ return name.trim().toLowerCase();
13294
+ }
13295
+ function toStoredProjectId(projectId) {
13296
+ const normalized = projectId?.trim() ?? "";
13297
+ return normalized || "";
13298
+ }
13299
+ function fromStoredProjectId(projectId) {
13300
+ const normalized = typeof projectId === "string" ? projectId.trim() : "";
13301
+ return normalized || null;
13302
+ }
13303
+ function getPresenceByAgent(db2, agent) {
13304
+ return db2.prepare(`
13305
+ SELECT * FROM agent_presence
13306
+ WHERE LOWER(agent) = ?
13307
+ ORDER BY last_seen_at DESC
13308
+ LIMIT 1
13309
+ `).get(agent);
13310
+ }
13311
+ function getPresenceByAgentAndProject(db2, agent, projectId) {
13312
+ return db2.prepare(`
13313
+ SELECT * FROM agent_presence
13314
+ WHERE LOWER(agent) = ? AND COALESCE(project_id, '') = ?
13315
+ ORDER BY last_seen_at DESC
13316
+ LIMIT 1
13317
+ `).get(agent, projectId);
13318
+ }
13067
13319
  function parsePresence(row) {
13068
13320
  let metadata = null;
13069
13321
  if (row.metadata) {
@@ -13082,7 +13334,7 @@ function parsePresence(row) {
13082
13334
  agent: row.agent,
13083
13335
  session_id: row.session_id ?? null,
13084
13336
  role: row.role || "agent",
13085
- project_id: row.project_id ?? null,
13337
+ project_id: fromStoredProjectId(row.project_id),
13086
13338
  status: row.status,
13087
13339
  last_seen_at: lastSeenAt,
13088
13340
  created_at: row.created_at || lastSeenAt,
@@ -13100,9 +13352,10 @@ function isAgentConflict(result) {
13100
13352
  }
13101
13353
  function registerAgent(name, sessionId, role, projectId) {
13102
13354
  const db2 = getDb();
13103
- const normalizedName = name.trim().toLowerCase();
13355
+ const normalizedName = normalizeAgentName(name);
13104
13356
  const result = db2.transaction(() => {
13105
- const existing = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(normalizedName);
13357
+ const existing = getPresenceByAgent(db2, normalizedName);
13358
+ const storedProjectId = toStoredProjectId(projectId ?? existing?.project_id);
13106
13359
  if (existing) {
13107
13360
  const lastSeenAt = existing.last_seen_at;
13108
13361
  const existingSessionId = existing.session_id;
@@ -13120,12 +13373,26 @@ function registerAgent(name, sessionId, role, projectId) {
13120
13373
  };
13121
13374
  }
13122
13375
  const tookOver = existingSessionId !== sessionId;
13123
- db2.prepare(`
13124
- UPDATE agent_presence
13125
- SET session_id = ?, role = ?, project_id = ?, last_seen_at = strftime('%Y-%m-%dT%H:%M:%f', 'now')
13126
- WHERE agent = ?
13127
- `).run(sessionId, role || existing.role || "agent", projectId ?? existing.project_id ?? null, normalizedName);
13128
- const updated = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(normalizedName);
13376
+ const existingId = existing.id;
13377
+ const target = getPresenceByAgentAndProject(db2, normalizedName, storedProjectId);
13378
+ if (target && target.id !== existingId) {
13379
+ db2.prepare(`
13380
+ UPDATE agent_presence
13381
+ SET session_id = ?, role = ?, status = 'online', last_seen_at = strftime('%Y-%m-%dT%H:%M:%f', 'now')
13382
+ WHERE id = ?
13383
+ `).run(sessionId, role || existing.role || "agent", target.id);
13384
+ db2.prepare("DELETE FROM agent_presence WHERE id = ?").run(existingId);
13385
+ } else {
13386
+ db2.prepare(`
13387
+ UPDATE agent_presence
13388
+ SET session_id = ?, role = ?, project_id = ?, status = 'online', last_seen_at = strftime('%Y-%m-%dT%H:%M:%f', 'now')
13389
+ WHERE id = ?
13390
+ `).run(sessionId, role || existing.role || "agent", storedProjectId, existingId);
13391
+ }
13392
+ const updated = getPresenceByAgentAndProject(db2, normalizedName, storedProjectId) ?? getPresenceByAgent(db2, normalizedName);
13393
+ if (!updated) {
13394
+ throw new Error(`Failed to update presence for agent "${normalizedName}"`);
13395
+ }
13129
13396
  return { agent: parsePresence(updated), created: false, took_over: tookOver };
13130
13397
  }
13131
13398
  const id = crypto.randomUUID().slice(0, 8);
@@ -13133,33 +13400,60 @@ function registerAgent(name, sessionId, role, projectId) {
13133
13400
  db2.prepare(`
13134
13401
  INSERT INTO agent_presence (id, agent, session_id, role, project_id, status, last_seen_at, created_at)
13135
13402
  VALUES (?, ?, ?, ?, ?, 'online', strftime('%Y-%m-%dT%H:%M:%f', 'now'), strftime('%Y-%m-%dT%H:%M:%f', 'now'))
13136
- `).run(id, normalizedName, sessionId, resolvedRole, projectId ?? null);
13137
- const created = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(normalizedName);
13403
+ `).run(id, normalizedName, sessionId, resolvedRole, storedProjectId);
13404
+ const created = getPresenceByAgentAndProject(db2, normalizedName, storedProjectId) ?? getPresenceByAgent(db2, normalizedName);
13405
+ if (!created) {
13406
+ throw new Error(`Failed to create presence for agent "${normalizedName}"`);
13407
+ }
13138
13408
  return { agent: parsePresence(created), created: true, took_over: false };
13139
13409
  });
13140
13410
  return result;
13141
13411
  }
13142
- function heartbeat(agent, status, metadata, sessionId) {
13412
+ function heartbeat(agent, status, metadata, sessionId, projectId) {
13143
13413
  const db2 = getDb();
13144
13414
  const metadataJson = metadata ? JSON.stringify(metadata) : null;
13145
13415
  const resolvedStatus = status || "online";
13146
- const normalizedAgent = agent.trim().toLowerCase();
13147
- const existing = db2.prepare("SELECT id FROM agent_presence WHERE agent = ?").get(agent);
13148
- const id = existing?.id || crypto.randomUUID().slice(0, 8);
13149
- db2.prepare(`
13150
- INSERT INTO agent_presence (id, agent, session_id, role, status, last_seen_at, created_at, metadata)
13151
- VALUES (?, ?, ?, 'agent', ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'), strftime('%Y-%m-%dT%H:%M:%f', 'now'), ?)
13152
- ON CONFLICT(agent) DO UPDATE SET
13153
- status = excluded.status,
13154
- last_seen_at = excluded.last_seen_at,
13155
- session_id = COALESCE(excluded.session_id, agent_presence.session_id),
13156
- metadata = excluded.metadata
13157
- `).run(id, normalizedAgent, sessionId ?? null, resolvedStatus, metadataJson);
13416
+ const normalizedAgent = normalizeAgentName(agent);
13417
+ db2.transaction(() => {
13418
+ const existing = getPresenceByAgent(db2, normalizedAgent);
13419
+ const storedProjectId = toStoredProjectId(projectId ?? existing?.project_id);
13420
+ const id = existing?.id || crypto.randomUUID().slice(0, 8);
13421
+ if (existing) {
13422
+ const existingId = existing.id;
13423
+ const target = getPresenceByAgentAndProject(db2, normalizedAgent, storedProjectId);
13424
+ if (target && target.id !== existingId) {
13425
+ db2.prepare(`
13426
+ UPDATE agent_presence
13427
+ SET status = ?,
13428
+ last_seen_at = strftime('%Y-%m-%dT%H:%M:%f', 'now'),
13429
+ session_id = COALESCE(?, session_id),
13430
+ metadata = ?
13431
+ WHERE id = ?
13432
+ `).run(resolvedStatus, sessionId ?? null, metadataJson, target.id);
13433
+ db2.prepare("DELETE FROM agent_presence WHERE id = ?").run(existingId);
13434
+ return;
13435
+ }
13436
+ db2.prepare(`
13437
+ UPDATE agent_presence
13438
+ SET status = ?,
13439
+ last_seen_at = strftime('%Y-%m-%dT%H:%M:%f', 'now'),
13440
+ session_id = COALESCE(?, session_id),
13441
+ metadata = ?,
13442
+ project_id = ?
13443
+ WHERE id = ?
13444
+ `).run(resolvedStatus, sessionId ?? null, metadataJson, storedProjectId, existingId);
13445
+ return;
13446
+ }
13447
+ db2.prepare(`
13448
+ INSERT INTO agent_presence (id, agent, session_id, role, project_id, status, last_seen_at, created_at, metadata)
13449
+ VALUES (?, ?, ?, 'agent', ?, ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'), strftime('%Y-%m-%dT%H:%M:%f', 'now'), ?)
13450
+ `).run(id, normalizedAgent, sessionId ?? null, storedProjectId, resolvedStatus, metadataJson);
13451
+ });
13158
13452
  }
13159
13453
  function getPresence(agent) {
13160
13454
  const db2 = getDb();
13161
- const normalizedAgent = agent.trim().toLowerCase();
13162
- const row = db2.prepare("SELECT * FROM agent_presence WHERE LOWER(agent) = ?").get(normalizedAgent);
13455
+ const normalizedAgent = normalizeAgentName(agent);
13456
+ const row = getPresenceByAgent(db2, normalizedAgent);
13163
13457
  return row ? parsePresence(row) : null;
13164
13458
  }
13165
13459
  function listAgents(opts) {
@@ -13174,14 +13468,14 @@ function listAgents(opts) {
13174
13468
  }
13175
13469
  function removePresence(agent) {
13176
13470
  const db2 = getDb();
13177
- const normalizedAgent = agent.trim().toLowerCase();
13471
+ const normalizedAgent = normalizeAgentName(agent);
13178
13472
  const result = db2.prepare("DELETE FROM agent_presence WHERE LOWER(agent) = ?").run(normalizedAgent);
13179
13473
  return result.changes > 0;
13180
13474
  }
13181
13475
  function renameAgent(oldName, newName) {
13182
13476
  const db2 = getDb();
13183
- const normalizedOld = oldName.trim().toLowerCase();
13184
- const normalizedNew = newName.trim().toLowerCase();
13477
+ const normalizedOld = normalizeAgentName(oldName);
13478
+ const normalizedNew = normalizeAgentName(newName);
13185
13479
  const existing = db2.prepare("SELECT agent FROM agent_presence WHERE LOWER(agent) = ?").get(normalizedOld);
13186
13480
  if (!existing)
13187
13481
  return false;
@@ -13888,13 +14182,13 @@ var gatherTrainingData = async (options = {}) => {
13888
14182
  };
13889
14183
  // src/lib/model-config.ts
13890
14184
  init_db();
13891
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync7, existsSync as existsSync6 } from "fs";
14185
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync7, existsSync as existsSync7 } from "fs";
13892
14186
  import { join as join10 } from "path";
13893
14187
  var DEFAULT_MODEL = "gpt-4o-mini";
13894
14188
  var CONFIG_DIR3 = getDataDir2();
13895
14189
  var CONFIG_PATH2 = join10(CONFIG_DIR3, "config.json");
13896
14190
  function readConfig() {
13897
- if (!existsSync6(CONFIG_PATH2))
14191
+ if (!existsSync7(CONFIG_PATH2))
13898
14192
  return {};
13899
14193
  try {
13900
14194
  return JSON.parse(readFileSync4(CONFIG_PATH2, "utf-8"));
@@ -13903,7 +14197,7 @@ function readConfig() {
13903
14197
  }
13904
14198
  }
13905
14199
  function writeConfig(config) {
13906
- if (!existsSync6(CONFIG_DIR3)) {
14200
+ if (!existsSync7(CONFIG_DIR3)) {
13907
14201
  mkdirSync7(CONFIG_DIR3, { recursive: true });
13908
14202
  }
13909
14203
  writeFileSync3(CONFIG_PATH2, JSON.stringify(config, null, 2), "utf-8");