@hasna/conversations 0.2.1 → 0.2.3

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,39 +5,60 @@ 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);
@@ -3646,10 +3667,12 @@ function renameAgent(oldName, newName) {
3646
3667
  // src/lib/locks.ts
3647
3668
  init_db();
3648
3669
  var DEFAULT_LOCK_EXPIRY_MS = 5 * 60 * 1000;
3670
+ var STALE_HEARTBEAT_SECONDS = 30 * 60;
3649
3671
  function acquireLock(resourceType, resourceId, agentId, lockType = "advisory", expiryMs = DEFAULT_LOCK_EXPIRY_MS) {
3650
3672
  const db2 = getDb();
3651
3673
  return db2.transaction(() => {
3652
3674
  cleanExpiredLocks();
3675
+ releaseStaleAgentLocks();
3653
3676
  const existing = db2.prepare(`
3654
3677
  SELECT * FROM resource_locks
3655
3678
  WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
@@ -3676,6 +3699,55 @@ function acquireLock(resourceType, resourceId, agentId, lockType = "advisory", e
3676
3699
  return { acquired: true, lock };
3677
3700
  }).immediate();
3678
3701
  }
3702
+ function bulkAcquireLock(resources, agentId) {
3703
+ const db2 = getDb();
3704
+ return db2.transaction(() => {
3705
+ cleanExpiredLocks();
3706
+ releaseStaleAgentLocks();
3707
+ const acquired = [];
3708
+ for (const { resource_type, resource_id, lock_type = "advisory", expiry_ms = DEFAULT_LOCK_EXPIRY_MS } of resources) {
3709
+ const existing = db2.prepare(`
3710
+ SELECT * FROM resource_locks
3711
+ WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
3712
+ `).get(resource_type, resource_id, lock_type);
3713
+ if (existing && existing.agent_id !== agentId) {
3714
+ throw { _bulkConflict: true, resource_type, resource_id, held_by: existing.agent_id };
3715
+ }
3716
+ const expiresAt = new Date(Date.now() + expiry_ms).toISOString().slice(0, -1);
3717
+ if (existing) {
3718
+ db2.prepare(`
3719
+ UPDATE resource_locks SET expires_at = ?, locked_at = strftime('%Y-%m-%dT%H:%M:%f', 'now')
3720
+ WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
3721
+ `).run(expiresAt, resource_type, resource_id, lock_type);
3722
+ } else {
3723
+ db2.prepare(`
3724
+ INSERT INTO resource_locks (resource_type, resource_id, agent_id, lock_type, locked_at, expires_at)
3725
+ VALUES (?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'), ?)
3726
+ `).run(resource_type, resource_id, agentId, lock_type, expiresAt);
3727
+ }
3728
+ const lock = db2.prepare(`
3729
+ SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = ?
3730
+ `).get(resource_type, resource_id, lock_type);
3731
+ acquired.push(lock);
3732
+ }
3733
+ return { acquired: true, locks: acquired };
3734
+ }).immediate();
3735
+ }
3736
+ function tryBulkAcquireLock(resources, agentId) {
3737
+ try {
3738
+ return bulkAcquireLock(resources, agentId);
3739
+ } catch (err) {
3740
+ const e = err;
3741
+ if (e?._bulkConflict) {
3742
+ return {
3743
+ acquired: false,
3744
+ locks: [],
3745
+ blocked_by: { resource_type: e.resource_type, resource_id: e.resource_id, held_by: e.held_by }
3746
+ };
3747
+ }
3748
+ throw err;
3749
+ }
3750
+ }
3679
3751
  function releaseLock(resourceType, resourceId, agentId) {
3680
3752
  const db2 = getDb();
3681
3753
  const result = db2.prepare(`
@@ -3687,6 +3759,7 @@ function releaseLock(resourceType, resourceId, agentId) {
3687
3759
  function checkLock(resourceType, resourceId) {
3688
3760
  const db2 = getDb();
3689
3761
  cleanExpiredLocks();
3762
+ releaseStaleAgentLocks();
3690
3763
  return db2.prepare(`
3691
3764
  SELECT * FROM resource_locks
3692
3765
  WHERE resource_type = ? AND resource_id = ?
@@ -3694,6 +3767,17 @@ function checkLock(resourceType, resourceId) {
3694
3767
  LIMIT 1
3695
3768
  `).get(resourceType, resourceId);
3696
3769
  }
3770
+ function releaseStaleAgentLocks() {
3771
+ const db2 = getDb();
3772
+ const result = db2.prepare(`
3773
+ DELETE FROM resource_locks
3774
+ WHERE LOWER(agent_id) IN (
3775
+ SELECT LOWER(agent) FROM agent_presence
3776
+ WHERE last_seen_at < strftime('%Y-%m-%dT%H:%M:%f', 'now', '-${STALE_HEARTBEAT_SECONDS} seconds')
3777
+ )
3778
+ `).run();
3779
+ return result.changes;
3780
+ }
3697
3781
  function cleanExpiredLocks() {
3698
3782
  const db2 = getDb();
3699
3783
  const result = db2.prepare(`
@@ -3704,6 +3788,7 @@ function cleanExpiredLocks() {
3704
3788
  function listLocks(opts) {
3705
3789
  const db2 = getDb();
3706
3790
  cleanExpiredLocks();
3791
+ releaseStaleAgentLocks();
3707
3792
  let query = "SELECT * FROM resource_locks WHERE 1=1";
3708
3793
  const params = [];
3709
3794
  if (opts?.resource_type) {
@@ -3717,6 +3802,31 @@ function listLocks(opts) {
3717
3802
  query += " ORDER BY locked_at ASC";
3718
3803
  return db2.prepare(query).all(...params);
3719
3804
  }
3805
+ function listLocksEnriched(opts) {
3806
+ const locks = listLocks(opts);
3807
+ const db2 = getDb();
3808
+ const nowMs = Date.now();
3809
+ return locks.map((lock) => {
3810
+ const lockedMs = new Date(lock.locked_at + "Z").getTime();
3811
+ const expiresMs = new Date(lock.expires_at + "Z").getTime();
3812
+ const presenceRow = db2.prepare(`
3813
+ SELECT role, status, last_seen_at, project_id FROM agent_presence WHERE LOWER(agent) = LOWER(?)
3814
+ `).get(lock.agent_id);
3815
+ const agent = presenceRow ? {
3816
+ role: presenceRow.role ?? null,
3817
+ status: presenceRow.status ?? null,
3818
+ online: presenceRow.last_seen_at ? nowMs - new Date(presenceRow.last_seen_at + "Z").getTime() < 60000 : false,
3819
+ last_seen_at: presenceRow.last_seen_at ?? null,
3820
+ project_id: presenceRow.project_id ?? null
3821
+ } : null;
3822
+ return {
3823
+ ...lock,
3824
+ locked_seconds_ago: Math.round((nowMs - lockedMs) / 1000),
3825
+ expires_in_seconds: Math.round((expiresMs - nowMs) / 1000),
3826
+ agent
3827
+ };
3828
+ });
3829
+ }
3720
3830
  // src/lib/hot.ts
3721
3831
  init_db();
3722
3832
  function computeHotness(sessionId) {
@@ -4201,6 +4311,7 @@ export {
4201
4311
  updateProject,
4202
4312
  unpinMessage,
4203
4313
  unarchiveSpace,
4314
+ tryBulkAcquireLock,
4204
4315
  startPolling,
4205
4316
  sendMessage,
4206
4317
  searchMessages,
@@ -4209,6 +4320,7 @@ export {
4209
4320
  renameAgent,
4210
4321
  removeReaction,
4211
4322
  removePresence,
4323
+ releaseStaleAgentLocks,
4212
4324
  releaseLock,
4213
4325
  registerAgent,
4214
4326
  readMessages,
@@ -4220,6 +4332,7 @@ export {
4220
4332
  listSpaces,
4221
4333
  listSessions,
4222
4334
  listProjects,
4335
+ listLocksEnriched,
4223
4336
  listLocks,
4224
4337
  listHotSessions,
4225
4338
  listAgents,
@@ -6,15 +6,48 @@ export interface ResourceLock {
6
6
  locked_at: string;
7
7
  expires_at: string;
8
8
  }
9
+ export interface BulkLockRequest {
10
+ resource_type: string;
11
+ resource_id: string;
12
+ lock_type?: "advisory" | "exclusive";
13
+ expiry_ms?: number;
14
+ }
15
+ export interface BulkAcquireResult {
16
+ acquired: boolean;
17
+ locks: ResourceLock[];
18
+ blocked_by?: {
19
+ resource_type: string;
20
+ resource_id: string;
21
+ held_by: string;
22
+ };
23
+ }
24
+ export interface EnrichedLock extends ResourceLock {
25
+ locked_seconds_ago: number;
26
+ expires_in_seconds: number;
27
+ agent: {
28
+ role: string | null;
29
+ status: string | null;
30
+ online: boolean;
31
+ last_seen_at: string | null;
32
+ project_id: string | null;
33
+ } | null;
34
+ }
9
35
  export declare function acquireLock(resourceType: string, resourceId: string, agentId: string, lockType?: "advisory" | "exclusive", expiryMs?: number): {
10
36
  acquired: boolean;
11
37
  lock: ResourceLock | null;
12
38
  held_by?: string;
13
39
  };
40
+ export declare function bulkAcquireLock(resources: BulkLockRequest[], agentId: string): BulkAcquireResult;
41
+ export declare function tryBulkAcquireLock(resources: BulkLockRequest[], agentId: string): BulkAcquireResult;
14
42
  export declare function releaseLock(resourceType: string, resourceId: string, agentId: string): boolean;
15
43
  export declare function checkLock(resourceType: string, resourceId: string): ResourceLock | null;
44
+ export declare function releaseStaleAgentLocks(): number;
16
45
  export declare function cleanExpiredLocks(): number;
17
46
  export declare function listLocks(opts?: {
18
47
  resource_type?: string;
19
48
  agent_id?: string;
20
49
  }): ResourceLock[];
50
+ export declare function listLocksEnriched(opts?: {
51
+ resource_type?: string;
52
+ agent_id?: string;
53
+ }): EnrichedLock[];
@@ -7,7 +7,34 @@ export declare function markRead(ids: number[], reader: string): number;
7
7
  export declare function markSessionRead(sessionId: string, reader: string): number;
8
8
  export declare function markSpaceRead(spaceName: string, reader: string): number;
9
9
  export declare function getMessageById(id: number): Message | null;
10
+ export declare function markReadByIds(ids: number[]): number;
10
11
  export declare function markAllRead(agent: string): number;
12
+ export interface DigestMessage {
13
+ id: number;
14
+ from: string;
15
+ created_at: string;
16
+ preview: string;
17
+ priority: string;
18
+ has_attachments: boolean;
19
+ space?: string | null;
20
+ to?: string | null;
21
+ unread: boolean;
22
+ }
23
+ export interface DigestResult {
24
+ messages: DigestMessage[];
25
+ total_unread: number;
26
+ shown: number;
27
+ }
28
+ export interface ReadDigestOptions {
29
+ space?: string;
30
+ session_id?: string;
31
+ to?: string;
32
+ since?: string;
33
+ limit?: number;
34
+ unread_only?: boolean;
35
+ project_id?: string;
36
+ }
37
+ export declare function readDigest(opts?: ReadDigestOptions): DigestResult;
11
38
  export interface ExportMessagesOptions {
12
39
  space?: string;
13
40
  session_id?: string;
@@ -29,3 +56,14 @@ export declare function getPinnedMessages(opts?: {
29
56
  export declare function getUnreadBlockers(agent: string): Message[];
30
57
  export declare function getThreadReplies(messageId: number): Message[];
31
58
  export declare function searchMessages(opts: SearchMessagesOptions): SearchResult[];
59
+ export interface UnreadCount {
60
+ space: string;
61
+ unread_count: number;
62
+ latest_message_at: string | null;
63
+ }
64
+ /**
65
+ * Get unread message counts per space — lightweight alternative to read_messages.
66
+ * Returns only spaces where the agent is a member (via space_members) or has received messages.
67
+ * If agent is omitted, returns counts for all spaces.
68
+ */
69
+ export declare function listUnreadCounts(agent?: string): UnreadCount[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/conversations",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Real-time CLI messaging for AI agents",
5
5
  "type": "module",
6
6
  "bin": {