@hasna/conversations 0.1.26 → 0.1.28

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 CHANGED
@@ -216,7 +216,8 @@ function getDb() {
216
216
  db.exec("ALTER TABLE agent_presence ADD COLUMN role TEXT NOT NULL DEFAULT 'agent'");
217
217
  }
218
218
  if (!presenceColNames.includes("created_at")) {
219
- db.exec("ALTER TABLE agent_presence ADD COLUMN created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now'))");
219
+ db.exec("ALTER TABLE agent_presence ADD COLUMN created_at TEXT NOT NULL DEFAULT ''");
220
+ db.exec("UPDATE agent_presence SET created_at = last_seen_at WHERE created_at = ''");
220
221
  }
221
222
  const ftsExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='messages_fts'").get();
222
223
  if (!ftsExists) {
package/bin/index.js CHANGED
@@ -2070,7 +2070,8 @@ function getDb() {
2070
2070
  db.exec("ALTER TABLE agent_presence ADD COLUMN role TEXT NOT NULL DEFAULT 'agent'");
2071
2071
  }
2072
2072
  if (!presenceColNames.includes("created_at")) {
2073
- db.exec("ALTER TABLE agent_presence ADD COLUMN created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now'))");
2073
+ db.exec("ALTER TABLE agent_presence ADD COLUMN created_at TEXT NOT NULL DEFAULT ''");
2074
+ db.exec("UPDATE agent_presence SET created_at = last_seen_at WHERE created_at = ''");
2074
2075
  }
2075
2076
  const ftsExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='messages_fts'").get();
2076
2077
  if (!ftsExists) {
@@ -3305,6 +3306,14 @@ function resolveIdentity(explicit) {
3305
3306
  return envValue;
3306
3307
  return getAutoName();
3307
3308
  }
3309
+ function updateCachedAutoName(newName) {
3310
+ cachedAutoName = newName;
3311
+ try {
3312
+ mkdirSync3(dirname2(AGENT_ID_FILE), { recursive: true });
3313
+ writeFileSync(AGENT_ID_FILE, newName + `
3314
+ `, "utf-8");
3315
+ } catch {}
3316
+ }
3308
3317
  var AGENT_ID_FILE, cachedAutoName = null;
3309
3318
  var init_identity = __esm(() => {
3310
3319
  init_names();
@@ -3344,16 +3353,22 @@ function isActiveSession(lastSeenAt) {
3344
3353
  }
3345
3354
  function registerAgent(name, sessionId, role) {
3346
3355
  const db2 = getDb();
3347
- const existing = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(name);
3356
+ const normalizedName = name.trim().toLowerCase();
3357
+ const existing = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(normalizedName);
3348
3358
  if (existing) {
3349
3359
  const lastSeenAt = existing.last_seen_at;
3350
3360
  const existingSessionId = existing.session_id;
3351
3361
  if (isActiveSession(lastSeenAt) && existingSessionId && existingSessionId !== sessionId) {
3352
3362
  return {
3363
+ conflict: true,
3353
3364
  error: "agent_conflict",
3354
- message: `Agent "${name}" is already active (last seen: ${lastSeenAt}). Wait 30 minutes or use force takeover.`,
3365
+ message: `Agent "${normalizedName}" is already active (last seen: ${lastSeenAt}). Wait 30 minutes or use force takeover.`,
3366
+ existing_id: existing.id,
3367
+ existing_name: normalizedName,
3355
3368
  existing_session_id: existingSessionId,
3356
- last_seen_at: lastSeenAt
3369
+ last_seen_at: lastSeenAt,
3370
+ session_hint: existingSessionId ? existingSessionId.slice(0, 8) : null,
3371
+ working_dir: null
3357
3372
  };
3358
3373
  }
3359
3374
  const tookOver = existingSessionId !== sessionId;
@@ -3361,8 +3376,8 @@ function registerAgent(name, sessionId, role) {
3361
3376
  UPDATE agent_presence
3362
3377
  SET session_id = ?, role = ?, last_seen_at = strftime('%Y-%m-%dT%H:%M:%f', 'now')
3363
3378
  WHERE agent = ?
3364
- `).run(sessionId, role || existing.role || "agent", name);
3365
- const updated = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(name);
3379
+ `).run(sessionId, role || existing.role || "agent", normalizedName);
3380
+ const updated = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(normalizedName);
3366
3381
  return { agent: parsePresence(updated), created: false, took_over: tookOver };
3367
3382
  }
3368
3383
  const id = crypto.randomUUID().slice(0, 8);
@@ -3370,14 +3385,15 @@ function registerAgent(name, sessionId, role) {
3370
3385
  db2.prepare(`
3371
3386
  INSERT INTO agent_presence (id, agent, session_id, role, status, last_seen_at, created_at)
3372
3387
  VALUES (?, ?, ?, ?, 'online', strftime('%Y-%m-%dT%H:%M:%f', 'now'), strftime('%Y-%m-%dT%H:%M:%f', 'now'))
3373
- `).run(id, name, sessionId, resolvedRole);
3374
- const created = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(name);
3388
+ `).run(id, normalizedName, sessionId, resolvedRole);
3389
+ const created = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(normalizedName);
3375
3390
  return { agent: parsePresence(created), created: true, took_over: false };
3376
3391
  }
3377
3392
  function heartbeat(agent, status, metadata, sessionId) {
3378
3393
  const db2 = getDb();
3379
3394
  const metadataJson = metadata ? JSON.stringify(metadata) : null;
3380
3395
  const resolvedStatus = status || "online";
3396
+ const normalizedAgent = agent.trim().toLowerCase();
3381
3397
  const existing = db2.prepare("SELECT id FROM agent_presence WHERE agent = ?").get(agent);
3382
3398
  const id = existing?.id || crypto.randomUUID().slice(0, 8);
3383
3399
  db2.prepare(`
@@ -3388,11 +3404,12 @@ function heartbeat(agent, status, metadata, sessionId) {
3388
3404
  last_seen_at = excluded.last_seen_at,
3389
3405
  session_id = COALESCE(excluded.session_id, agent_presence.session_id),
3390
3406
  metadata = excluded.metadata
3391
- `).run(id, agent, sessionId ?? null, resolvedStatus, metadataJson);
3407
+ `).run(id, normalizedAgent, sessionId ?? null, resolvedStatus, metadataJson);
3392
3408
  }
3393
3409
  function getPresence(agent) {
3394
3410
  const db2 = getDb();
3395
- const row = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(agent);
3411
+ const normalizedAgent = agent.trim().toLowerCase();
3412
+ const row = db2.prepare("SELECT * FROM agent_presence WHERE LOWER(agent) = ?").get(normalizedAgent);
3396
3413
  return row ? parsePresence(row) : null;
3397
3414
  }
3398
3415
  function listAgents(opts) {
@@ -3407,18 +3424,21 @@ function listAgents(opts) {
3407
3424
  }
3408
3425
  function removePresence(agent) {
3409
3426
  const db2 = getDb();
3410
- const result = db2.prepare("DELETE FROM agent_presence WHERE agent = ?").run(agent);
3427
+ const normalizedAgent = agent.trim().toLowerCase();
3428
+ const result = db2.prepare("DELETE FROM agent_presence WHERE LOWER(agent) = ?").run(normalizedAgent);
3411
3429
  return result.changes > 0;
3412
3430
  }
3413
3431
  function renameAgent(oldName, newName) {
3414
3432
  const db2 = getDb();
3415
- const existing = db2.prepare("SELECT agent FROM agent_presence WHERE agent = ?").get(oldName);
3433
+ const normalizedOld = oldName.trim().toLowerCase();
3434
+ const normalizedNew = newName.trim().toLowerCase();
3435
+ const existing = db2.prepare("SELECT agent FROM agent_presence WHERE LOWER(agent) = ?").get(normalizedOld);
3416
3436
  if (!existing)
3417
3437
  return false;
3418
- const conflict = db2.prepare("SELECT agent FROM agent_presence WHERE agent = ?").get(newName);
3438
+ const conflict = db2.prepare("SELECT agent FROM agent_presence WHERE LOWER(agent) = ?").get(normalizedNew);
3419
3439
  if (conflict)
3420
- throw new Error(`Agent "${newName}" already exists`);
3421
- db2.prepare("UPDATE agent_presence SET agent = ? WHERE agent = ?").run(newName, oldName);
3440
+ throw new Error(`Agent "${normalizedNew}" already exists`);
3441
+ db2.prepare("UPDATE agent_presence SET agent = ? WHERE LOWER(agent) = ?").run(normalizedNew, normalizedOld);
3422
3442
  return true;
3423
3443
  }
3424
3444
  var ONLINE_THRESHOLD_SECONDS = 60, CONFLICT_THRESHOLD_SECONDS;
@@ -3571,7 +3591,7 @@ var init_poll = __esm(() => {
3571
3591
  var require_package = __commonJS((exports, module) => {
3572
3592
  module.exports = {
3573
3593
  name: "@hasna/conversations",
3574
- version: "0.1.26",
3594
+ version: "0.1.28",
3575
3595
  description: "Real-time CLI messaging for AI agents",
3576
3596
  type: "module",
3577
3597
  bin: {
@@ -32548,7 +32568,7 @@ var init_mcp2 = __esm(() => {
32548
32568
  content: exports_external.string(),
32549
32569
  from: exports_external.string().optional(),
32550
32570
  priority: exports_external.string().optional(),
32551
- blocking: exports_external.boolean().optional()
32571
+ blocking: exports_external.coerce.boolean().optional()
32552
32572
  }
32553
32573
  }, async (args) => {
32554
32574
  const { from: fromParam, to, content, priority, blocking } = args;
@@ -32572,8 +32592,8 @@ var init_mcp2 = __esm(() => {
32572
32592
  to: exports_external.string().optional(),
32573
32593
  space: exports_external.string().optional(),
32574
32594
  since: exports_external.string().optional(),
32575
- limit: exports_external.number().optional(),
32576
- unread_only: exports_external.boolean().optional()
32595
+ limit: exports_external.coerce.number().optional(),
32596
+ unread_only: exports_external.coerce.boolean().optional()
32577
32597
  }
32578
32598
  }, async (args) => {
32579
32599
  const messages = readMessages(args);
@@ -32596,7 +32616,7 @@ var init_mcp2 = __esm(() => {
32596
32616
  server.registerTool("reply", {
32597
32617
  description: "Reply to a message by ID.",
32598
32618
  inputSchema: {
32599
- message_id: exports_external.number(),
32619
+ message_id: exports_external.coerce.number(),
32600
32620
  content: exports_external.string(),
32601
32621
  from: exports_external.string().optional()
32602
32622
  }
@@ -32627,8 +32647,8 @@ var init_mcp2 = __esm(() => {
32627
32647
  description: "Mark messages read by IDs or all.",
32628
32648
  inputSchema: {
32629
32649
  from: exports_external.string().optional(),
32630
- ids: exports_external.array(exports_external.number()).optional(),
32631
- all: exports_external.boolean().optional()
32650
+ ids: exports_external.array(exports_external.coerce.number()).optional(),
32651
+ all: exports_external.coerce.boolean().optional()
32632
32652
  }
32633
32653
  }, async (args) => {
32634
32654
  const { from: fromParam, ids, all } = args;
@@ -32655,7 +32675,7 @@ var init_mcp2 = __esm(() => {
32655
32675
  space: exports_external.string().optional(),
32656
32676
  from: exports_external.string().optional(),
32657
32677
  to: exports_external.string().optional(),
32658
- limit: exports_external.number().optional()
32678
+ limit: exports_external.coerce.number().optional()
32659
32679
  }
32660
32680
  }, async (args) => {
32661
32681
  const { query, space, from, to, limit } = args;
@@ -32716,7 +32736,7 @@ var init_mcp2 = __esm(() => {
32716
32736
  inputSchema: {
32717
32737
  project_id: exports_external.string().optional(),
32718
32738
  parent_id: exports_external.string().optional(),
32719
- include_archived: exports_external.boolean().optional()
32739
+ include_archived: exports_external.coerce.boolean().optional()
32720
32740
  }
32721
32741
  }, async (args) => {
32722
32742
  const { project_id, parent_id, include_archived } = args;
@@ -32742,7 +32762,7 @@ var init_mcp2 = __esm(() => {
32742
32762
  content: exports_external.string(),
32743
32763
  from: exports_external.string().optional(),
32744
32764
  priority: exports_external.string().optional(),
32745
- blocking: exports_external.boolean().optional()
32765
+ blocking: exports_external.coerce.boolean().optional()
32746
32766
  }
32747
32767
  }, async (args) => {
32748
32768
  const { from: fromParam, space, content, priority, blocking } = args;
@@ -32772,7 +32792,7 @@ var init_mcp2 = __esm(() => {
32772
32792
  inputSchema: {
32773
32793
  space: exports_external.string(),
32774
32794
  since: exports_external.string().optional(),
32775
- limit: exports_external.number().optional()
32795
+ limit: exports_external.coerce.number().optional()
32776
32796
  }
32777
32797
  }, async (args) => {
32778
32798
  const { space, since, limit } = args;
@@ -33082,7 +33102,7 @@ var init_mcp2 = __esm(() => {
33082
33102
  server.registerTool("delete_message", {
33083
33103
  description: "Delete a message (sender only).",
33084
33104
  inputSchema: {
33085
- id: exports_external.number(),
33105
+ id: exports_external.coerce.number(),
33086
33106
  from: exports_external.string().optional()
33087
33107
  }
33088
33108
  }, async (args) => {
@@ -33102,7 +33122,7 @@ var init_mcp2 = __esm(() => {
33102
33122
  server.registerTool("edit_message", {
33103
33123
  description: "Edit message content (sender only).",
33104
33124
  inputSchema: {
33105
- id: exports_external.number(),
33125
+ id: exports_external.coerce.number(),
33106
33126
  content: exports_external.string(),
33107
33127
  from: exports_external.string().optional()
33108
33128
  }
@@ -33123,7 +33143,7 @@ var init_mcp2 = __esm(() => {
33123
33143
  server.registerTool("pin_message", {
33124
33144
  description: "Pin a message.",
33125
33145
  inputSchema: {
33126
- id: exports_external.number()
33146
+ id: exports_external.coerce.number()
33127
33147
  }
33128
33148
  }, async ({ id }) => {
33129
33149
  const msg = pinMessage(id);
@@ -33140,7 +33160,7 @@ var init_mcp2 = __esm(() => {
33140
33160
  server.registerTool("unpin_message", {
33141
33161
  description: "Unpin a message.",
33142
33162
  inputSchema: {
33143
- id: exports_external.number()
33163
+ id: exports_external.coerce.number()
33144
33164
  }
33145
33165
  }, async ({ id }) => {
33146
33166
  const msg = unpinMessage(id);
@@ -33159,7 +33179,7 @@ var init_mcp2 = __esm(() => {
33159
33179
  inputSchema: {
33160
33180
  space: exports_external.string().optional(),
33161
33181
  session_id: exports_external.string().optional(),
33162
- limit: exports_external.number().optional()
33182
+ limit: exports_external.coerce.number().optional()
33163
33183
  }
33164
33184
  }, async (args) => {
33165
33185
  const { space, session_id, limit } = args;
@@ -33199,7 +33219,7 @@ var init_mcp2 = __esm(() => {
33199
33219
  server.registerTool("list_agents", {
33200
33220
  description: "List agents with presence status.",
33201
33221
  inputSchema: {
33202
- online_only: exports_external.boolean().optional()
33222
+ online_only: exports_external.coerce.boolean().optional()
33203
33223
  }
33204
33224
  }, async (args) => {
33205
33225
  const { online_only } = args;
@@ -33266,6 +33286,9 @@ var init_mcp2 = __esm(() => {
33266
33286
  isError: true
33267
33287
  };
33268
33288
  }
33289
+ if (!fromParam) {
33290
+ updateCachedAutoName(newName);
33291
+ }
33269
33292
  return {
33270
33293
  content: [{ type: "text", text: JSON.stringify({ old_name: oldName, new_name: newName, renamed: true }) }]
33271
33294
  };
package/bin/mcp.js CHANGED
@@ -6702,7 +6702,8 @@ function getDb() {
6702
6702
  db.exec("ALTER TABLE agent_presence ADD COLUMN role TEXT NOT NULL DEFAULT 'agent'");
6703
6703
  }
6704
6704
  if (!presenceColNames.includes("created_at")) {
6705
- db.exec("ALTER TABLE agent_presence ADD COLUMN created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now'))");
6705
+ db.exec("ALTER TABLE agent_presence ADD COLUMN created_at TEXT NOT NULL DEFAULT ''");
6706
+ db.exec("UPDATE agent_presence SET created_at = last_seen_at WHERE created_at = ''");
6706
6707
  }
6707
6708
  const ftsExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='messages_fts'").get();
6708
6709
  if (!ftsExists) {
@@ -29733,6 +29734,14 @@ function resolveIdentity(explicit) {
29733
29734
  return envValue;
29734
29735
  return getAutoName();
29735
29736
  }
29737
+ function updateCachedAutoName(newName) {
29738
+ cachedAutoName = newName;
29739
+ try {
29740
+ mkdirSync3(dirname2(AGENT_ID_FILE), { recursive: true });
29741
+ writeFileSync(AGENT_ID_FILE, newName + `
29742
+ `, "utf-8");
29743
+ } catch {}
29744
+ }
29736
29745
 
29737
29746
  // src/lib/presence.ts
29738
29747
  init_db();
@@ -29770,16 +29779,22 @@ function isActiveSession(lastSeenAt) {
29770
29779
  }
29771
29780
  function registerAgent(name, sessionId, role) {
29772
29781
  const db2 = getDb();
29773
- const existing = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(name);
29782
+ const normalizedName = name.trim().toLowerCase();
29783
+ const existing = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(normalizedName);
29774
29784
  if (existing) {
29775
29785
  const lastSeenAt = existing.last_seen_at;
29776
29786
  const existingSessionId = existing.session_id;
29777
29787
  if (isActiveSession(lastSeenAt) && existingSessionId && existingSessionId !== sessionId) {
29778
29788
  return {
29789
+ conflict: true,
29779
29790
  error: "agent_conflict",
29780
- message: `Agent "${name}" is already active (last seen: ${lastSeenAt}). Wait 30 minutes or use force takeover.`,
29791
+ message: `Agent "${normalizedName}" is already active (last seen: ${lastSeenAt}). Wait 30 minutes or use force takeover.`,
29792
+ existing_id: existing.id,
29793
+ existing_name: normalizedName,
29781
29794
  existing_session_id: existingSessionId,
29782
- last_seen_at: lastSeenAt
29795
+ last_seen_at: lastSeenAt,
29796
+ session_hint: existingSessionId ? existingSessionId.slice(0, 8) : null,
29797
+ working_dir: null
29783
29798
  };
29784
29799
  }
29785
29800
  const tookOver = existingSessionId !== sessionId;
@@ -29787,8 +29802,8 @@ function registerAgent(name, sessionId, role) {
29787
29802
  UPDATE agent_presence
29788
29803
  SET session_id = ?, role = ?, last_seen_at = strftime('%Y-%m-%dT%H:%M:%f', 'now')
29789
29804
  WHERE agent = ?
29790
- `).run(sessionId, role || existing.role || "agent", name);
29791
- const updated = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(name);
29805
+ `).run(sessionId, role || existing.role || "agent", normalizedName);
29806
+ const updated = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(normalizedName);
29792
29807
  return { agent: parsePresence(updated), created: false, took_over: tookOver };
29793
29808
  }
29794
29809
  const id = crypto.randomUUID().slice(0, 8);
@@ -29796,14 +29811,15 @@ function registerAgent(name, sessionId, role) {
29796
29811
  db2.prepare(`
29797
29812
  INSERT INTO agent_presence (id, agent, session_id, role, status, last_seen_at, created_at)
29798
29813
  VALUES (?, ?, ?, ?, 'online', strftime('%Y-%m-%dT%H:%M:%f', 'now'), strftime('%Y-%m-%dT%H:%M:%f', 'now'))
29799
- `).run(id, name, sessionId, resolvedRole);
29800
- const created = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(name);
29814
+ `).run(id, normalizedName, sessionId, resolvedRole);
29815
+ const created = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(normalizedName);
29801
29816
  return { agent: parsePresence(created), created: true, took_over: false };
29802
29817
  }
29803
29818
  function heartbeat(agent, status, metadata, sessionId) {
29804
29819
  const db2 = getDb();
29805
29820
  const metadataJson = metadata ? JSON.stringify(metadata) : null;
29806
29821
  const resolvedStatus = status || "online";
29822
+ const normalizedAgent = agent.trim().toLowerCase();
29807
29823
  const existing = db2.prepare("SELECT id FROM agent_presence WHERE agent = ?").get(agent);
29808
29824
  const id = existing?.id || crypto.randomUUID().slice(0, 8);
29809
29825
  db2.prepare(`
@@ -29814,7 +29830,7 @@ function heartbeat(agent, status, metadata, sessionId) {
29814
29830
  last_seen_at = excluded.last_seen_at,
29815
29831
  session_id = COALESCE(excluded.session_id, agent_presence.session_id),
29816
29832
  metadata = excluded.metadata
29817
- `).run(id, agent, sessionId ?? null, resolvedStatus, metadataJson);
29833
+ `).run(id, normalizedAgent, sessionId ?? null, resolvedStatus, metadataJson);
29818
29834
  }
29819
29835
  function listAgents(opts) {
29820
29836
  const db2 = getDb();
@@ -29828,24 +29844,27 @@ function listAgents(opts) {
29828
29844
  }
29829
29845
  function removePresence(agent) {
29830
29846
  const db2 = getDb();
29831
- const result = db2.prepare("DELETE FROM agent_presence WHERE agent = ?").run(agent);
29847
+ const normalizedAgent = agent.trim().toLowerCase();
29848
+ const result = db2.prepare("DELETE FROM agent_presence WHERE LOWER(agent) = ?").run(normalizedAgent);
29832
29849
  return result.changes > 0;
29833
29850
  }
29834
29851
  function renameAgent(oldName, newName) {
29835
29852
  const db2 = getDb();
29836
- const existing = db2.prepare("SELECT agent FROM agent_presence WHERE agent = ?").get(oldName);
29853
+ const normalizedOld = oldName.trim().toLowerCase();
29854
+ const normalizedNew = newName.trim().toLowerCase();
29855
+ const existing = db2.prepare("SELECT agent FROM agent_presence WHERE LOWER(agent) = ?").get(normalizedOld);
29837
29856
  if (!existing)
29838
29857
  return false;
29839
- const conflict = db2.prepare("SELECT agent FROM agent_presence WHERE agent = ?").get(newName);
29858
+ const conflict = db2.prepare("SELECT agent FROM agent_presence WHERE LOWER(agent) = ?").get(normalizedNew);
29840
29859
  if (conflict)
29841
- throw new Error(`Agent "${newName}" already exists`);
29842
- db2.prepare("UPDATE agent_presence SET agent = ? WHERE agent = ?").run(newName, oldName);
29860
+ throw new Error(`Agent "${normalizedNew}" already exists`);
29861
+ db2.prepare("UPDATE agent_presence SET agent = ? WHERE LOWER(agent) = ?").run(normalizedNew, normalizedOld);
29843
29862
  return true;
29844
29863
  }
29845
29864
  // package.json
29846
29865
  var package_default = {
29847
29866
  name: "@hasna/conversations",
29848
- version: "0.1.26",
29867
+ version: "0.1.28",
29849
29868
  description: "Real-time CLI messaging for AI agents",
29850
29869
  type: "module",
29851
29870
  bin: {
@@ -29934,7 +29953,7 @@ server.registerTool("send_message", {
29934
29953
  content: exports_external.string(),
29935
29954
  from: exports_external.string().optional(),
29936
29955
  priority: exports_external.string().optional(),
29937
- blocking: exports_external.boolean().optional()
29956
+ blocking: exports_external.coerce.boolean().optional()
29938
29957
  }
29939
29958
  }, async (args) => {
29940
29959
  const { from: fromParam, to, content, priority, blocking } = args;
@@ -29958,8 +29977,8 @@ server.registerTool("read_messages", {
29958
29977
  to: exports_external.string().optional(),
29959
29978
  space: exports_external.string().optional(),
29960
29979
  since: exports_external.string().optional(),
29961
- limit: exports_external.number().optional(),
29962
- unread_only: exports_external.boolean().optional()
29980
+ limit: exports_external.coerce.number().optional(),
29981
+ unread_only: exports_external.coerce.boolean().optional()
29963
29982
  }
29964
29983
  }, async (args) => {
29965
29984
  const messages = readMessages(args);
@@ -29982,7 +30001,7 @@ server.registerTool("list_sessions", {
29982
30001
  server.registerTool("reply", {
29983
30002
  description: "Reply to a message by ID.",
29984
30003
  inputSchema: {
29985
- message_id: exports_external.number(),
30004
+ message_id: exports_external.coerce.number(),
29986
30005
  content: exports_external.string(),
29987
30006
  from: exports_external.string().optional()
29988
30007
  }
@@ -30013,8 +30032,8 @@ server.registerTool("mark_read", {
30013
30032
  description: "Mark messages read by IDs or all.",
30014
30033
  inputSchema: {
30015
30034
  from: exports_external.string().optional(),
30016
- ids: exports_external.array(exports_external.number()).optional(),
30017
- all: exports_external.boolean().optional()
30035
+ ids: exports_external.array(exports_external.coerce.number()).optional(),
30036
+ all: exports_external.coerce.boolean().optional()
30018
30037
  }
30019
30038
  }, async (args) => {
30020
30039
  const { from: fromParam, ids, all } = args;
@@ -30041,7 +30060,7 @@ server.registerTool("search_messages", {
30041
30060
  space: exports_external.string().optional(),
30042
30061
  from: exports_external.string().optional(),
30043
30062
  to: exports_external.string().optional(),
30044
- limit: exports_external.number().optional()
30063
+ limit: exports_external.coerce.number().optional()
30045
30064
  }
30046
30065
  }, async (args) => {
30047
30066
  const { query, space, from, to, limit } = args;
@@ -30102,7 +30121,7 @@ server.registerTool("list_spaces", {
30102
30121
  inputSchema: {
30103
30122
  project_id: exports_external.string().optional(),
30104
30123
  parent_id: exports_external.string().optional(),
30105
- include_archived: exports_external.boolean().optional()
30124
+ include_archived: exports_external.coerce.boolean().optional()
30106
30125
  }
30107
30126
  }, async (args) => {
30108
30127
  const { project_id, parent_id, include_archived } = args;
@@ -30128,7 +30147,7 @@ server.registerTool("send_to_space", {
30128
30147
  content: exports_external.string(),
30129
30148
  from: exports_external.string().optional(),
30130
30149
  priority: exports_external.string().optional(),
30131
- blocking: exports_external.boolean().optional()
30150
+ blocking: exports_external.coerce.boolean().optional()
30132
30151
  }
30133
30152
  }, async (args) => {
30134
30153
  const { from: fromParam, space, content, priority, blocking } = args;
@@ -30158,7 +30177,7 @@ server.registerTool("read_space", {
30158
30177
  inputSchema: {
30159
30178
  space: exports_external.string(),
30160
30179
  since: exports_external.string().optional(),
30161
- limit: exports_external.number().optional()
30180
+ limit: exports_external.coerce.number().optional()
30162
30181
  }
30163
30182
  }, async (args) => {
30164
30183
  const { space, since, limit } = args;
@@ -30468,7 +30487,7 @@ server.registerTool("delete_project", {
30468
30487
  server.registerTool("delete_message", {
30469
30488
  description: "Delete a message (sender only).",
30470
30489
  inputSchema: {
30471
- id: exports_external.number(),
30490
+ id: exports_external.coerce.number(),
30472
30491
  from: exports_external.string().optional()
30473
30492
  }
30474
30493
  }, async (args) => {
@@ -30488,7 +30507,7 @@ server.registerTool("delete_message", {
30488
30507
  server.registerTool("edit_message", {
30489
30508
  description: "Edit message content (sender only).",
30490
30509
  inputSchema: {
30491
- id: exports_external.number(),
30510
+ id: exports_external.coerce.number(),
30492
30511
  content: exports_external.string(),
30493
30512
  from: exports_external.string().optional()
30494
30513
  }
@@ -30509,7 +30528,7 @@ server.registerTool("edit_message", {
30509
30528
  server.registerTool("pin_message", {
30510
30529
  description: "Pin a message.",
30511
30530
  inputSchema: {
30512
- id: exports_external.number()
30531
+ id: exports_external.coerce.number()
30513
30532
  }
30514
30533
  }, async ({ id }) => {
30515
30534
  const msg = pinMessage(id);
@@ -30526,7 +30545,7 @@ server.registerTool("pin_message", {
30526
30545
  server.registerTool("unpin_message", {
30527
30546
  description: "Unpin a message.",
30528
30547
  inputSchema: {
30529
- id: exports_external.number()
30548
+ id: exports_external.coerce.number()
30530
30549
  }
30531
30550
  }, async ({ id }) => {
30532
30551
  const msg = unpinMessage(id);
@@ -30545,7 +30564,7 @@ server.registerTool("get_pinned_messages", {
30545
30564
  inputSchema: {
30546
30565
  space: exports_external.string().optional(),
30547
30566
  session_id: exports_external.string().optional(),
30548
- limit: exports_external.number().optional()
30567
+ limit: exports_external.coerce.number().optional()
30549
30568
  }
30550
30569
  }, async (args) => {
30551
30570
  const { space, session_id, limit } = args;
@@ -30585,7 +30604,7 @@ server.registerTool("heartbeat", {
30585
30604
  server.registerTool("list_agents", {
30586
30605
  description: "List agents with presence status.",
30587
30606
  inputSchema: {
30588
- online_only: exports_external.boolean().optional()
30607
+ online_only: exports_external.coerce.boolean().optional()
30589
30608
  }
30590
30609
  }, async (args) => {
30591
30610
  const { online_only } = args;
@@ -30652,6 +30671,9 @@ server.registerTool("rename_agent", {
30652
30671
  isError: true
30653
30672
  };
30654
30673
  }
30674
+ if (!fromParam) {
30675
+ updateCachedAutoName(newName);
30676
+ }
30655
30677
  return {
30656
30678
  content: [{ type: "text", text: JSON.stringify({ old_name: oldName, new_name: newName, renamed: true }) }]
30657
30679
  };
package/dist/index.d.ts CHANGED
@@ -18,5 +18,5 @@ export { startPolling, useSpaceMessages, } from "./lib/poll.js";
18
18
  export { resolveIdentity, requireIdentity, } from "./lib/identity.js";
19
19
  export { addReaction, removeReaction, getReactions, getReactionSummary, } from "./lib/reactions.js";
20
20
  export { fireWebhooks, } from "./lib/webhooks.js";
21
- export { heartbeat, registerAgent, getPresence, listAgents, removePresence, renameAgent, } from "./lib/presence.js";
21
+ export { heartbeat, registerAgent, isAgentConflict, getPresence, listAgents, removePresence, renameAgent, } from "./lib/presence.js";
22
22
  export type { Message, Session, Space, SpaceInfo, SpaceMember, Project, ProjectInfo, Priority, SendMessageOptions, ReadMessagesOptions, SearchMessagesOptions, AgentPresence, AgentConflictError, RegisterAgentResult, Reaction, Attachment, } from "./types.js";
package/dist/index.js CHANGED
@@ -229,7 +229,8 @@ function getDb() {
229
229
  db.exec("ALTER TABLE agent_presence ADD COLUMN role TEXT NOT NULL DEFAULT 'agent'");
230
230
  }
231
231
  if (!presenceColNames.includes("created_at")) {
232
- db.exec("ALTER TABLE agent_presence ADD COLUMN created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now'))");
232
+ db.exec("ALTER TABLE agent_presence ADD COLUMN created_at TEXT NOT NULL DEFAULT ''");
233
+ db.exec("UPDATE agent_presence SET created_at = last_seen_at WHERE created_at = ''");
233
234
  }
234
235
  const ftsExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='messages_fts'").get();
235
236
  if (!ftsExists) {
@@ -3455,18 +3456,27 @@ function isActiveSession(lastSeenAt) {
3455
3456
  const nowMs = Date.now();
3456
3457
  return nowMs - lastSeenMs < CONFLICT_THRESHOLD_SECONDS * 1000;
3457
3458
  }
3459
+ function isAgentConflict(result) {
3460
+ return result.conflict === true;
3461
+ }
3458
3462
  function registerAgent(name, sessionId, role) {
3459
3463
  const db2 = getDb();
3460
- const existing = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(name);
3464
+ const normalizedName = name.trim().toLowerCase();
3465
+ const existing = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(normalizedName);
3461
3466
  if (existing) {
3462
3467
  const lastSeenAt = existing.last_seen_at;
3463
3468
  const existingSessionId = existing.session_id;
3464
3469
  if (isActiveSession(lastSeenAt) && existingSessionId && existingSessionId !== sessionId) {
3465
3470
  return {
3471
+ conflict: true,
3466
3472
  error: "agent_conflict",
3467
- message: `Agent "${name}" is already active (last seen: ${lastSeenAt}). Wait 30 minutes or use force takeover.`,
3473
+ message: `Agent "${normalizedName}" is already active (last seen: ${lastSeenAt}). Wait 30 minutes or use force takeover.`,
3474
+ existing_id: existing.id,
3475
+ existing_name: normalizedName,
3468
3476
  existing_session_id: existingSessionId,
3469
- last_seen_at: lastSeenAt
3477
+ last_seen_at: lastSeenAt,
3478
+ session_hint: existingSessionId ? existingSessionId.slice(0, 8) : null,
3479
+ working_dir: null
3470
3480
  };
3471
3481
  }
3472
3482
  const tookOver = existingSessionId !== sessionId;
@@ -3474,8 +3484,8 @@ function registerAgent(name, sessionId, role) {
3474
3484
  UPDATE agent_presence
3475
3485
  SET session_id = ?, role = ?, last_seen_at = strftime('%Y-%m-%dT%H:%M:%f', 'now')
3476
3486
  WHERE agent = ?
3477
- `).run(sessionId, role || existing.role || "agent", name);
3478
- const updated = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(name);
3487
+ `).run(sessionId, role || existing.role || "agent", normalizedName);
3488
+ const updated = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(normalizedName);
3479
3489
  return { agent: parsePresence(updated), created: false, took_over: tookOver };
3480
3490
  }
3481
3491
  const id = crypto.randomUUID().slice(0, 8);
@@ -3483,14 +3493,15 @@ function registerAgent(name, sessionId, role) {
3483
3493
  db2.prepare(`
3484
3494
  INSERT INTO agent_presence (id, agent, session_id, role, status, last_seen_at, created_at)
3485
3495
  VALUES (?, ?, ?, ?, 'online', strftime('%Y-%m-%dT%H:%M:%f', 'now'), strftime('%Y-%m-%dT%H:%M:%f', 'now'))
3486
- `).run(id, name, sessionId, resolvedRole);
3487
- const created = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(name);
3496
+ `).run(id, normalizedName, sessionId, resolvedRole);
3497
+ const created = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(normalizedName);
3488
3498
  return { agent: parsePresence(created), created: true, took_over: false };
3489
3499
  }
3490
3500
  function heartbeat(agent, status, metadata, sessionId) {
3491
3501
  const db2 = getDb();
3492
3502
  const metadataJson = metadata ? JSON.stringify(metadata) : null;
3493
3503
  const resolvedStatus = status || "online";
3504
+ const normalizedAgent = agent.trim().toLowerCase();
3494
3505
  const existing = db2.prepare("SELECT id FROM agent_presence WHERE agent = ?").get(agent);
3495
3506
  const id = existing?.id || crypto.randomUUID().slice(0, 8);
3496
3507
  db2.prepare(`
@@ -3501,11 +3512,12 @@ function heartbeat(agent, status, metadata, sessionId) {
3501
3512
  last_seen_at = excluded.last_seen_at,
3502
3513
  session_id = COALESCE(excluded.session_id, agent_presence.session_id),
3503
3514
  metadata = excluded.metadata
3504
- `).run(id, agent, sessionId ?? null, resolvedStatus, metadataJson);
3515
+ `).run(id, normalizedAgent, sessionId ?? null, resolvedStatus, metadataJson);
3505
3516
  }
3506
3517
  function getPresence(agent) {
3507
3518
  const db2 = getDb();
3508
- const row = db2.prepare("SELECT * FROM agent_presence WHERE agent = ?").get(agent);
3519
+ const normalizedAgent = agent.trim().toLowerCase();
3520
+ const row = db2.prepare("SELECT * FROM agent_presence WHERE LOWER(agent) = ?").get(normalizedAgent);
3509
3521
  return row ? parsePresence(row) : null;
3510
3522
  }
3511
3523
  function listAgents(opts) {
@@ -3520,18 +3532,21 @@ function listAgents(opts) {
3520
3532
  }
3521
3533
  function removePresence(agent) {
3522
3534
  const db2 = getDb();
3523
- const result = db2.prepare("DELETE FROM agent_presence WHERE agent = ?").run(agent);
3535
+ const normalizedAgent = agent.trim().toLowerCase();
3536
+ const result = db2.prepare("DELETE FROM agent_presence WHERE LOWER(agent) = ?").run(normalizedAgent);
3524
3537
  return result.changes > 0;
3525
3538
  }
3526
3539
  function renameAgent(oldName, newName) {
3527
3540
  const db2 = getDb();
3528
- const existing = db2.prepare("SELECT agent FROM agent_presence WHERE agent = ?").get(oldName);
3541
+ const normalizedOld = oldName.trim().toLowerCase();
3542
+ const normalizedNew = newName.trim().toLowerCase();
3543
+ const existing = db2.prepare("SELECT agent FROM agent_presence WHERE LOWER(agent) = ?").get(normalizedOld);
3529
3544
  if (!existing)
3530
3545
  return false;
3531
- const conflict = db2.prepare("SELECT agent FROM agent_presence WHERE agent = ?").get(newName);
3546
+ const conflict = db2.prepare("SELECT agent FROM agent_presence WHERE LOWER(agent) = ?").get(normalizedNew);
3532
3547
  if (conflict)
3533
- throw new Error(`Agent "${newName}" already exists`);
3534
- db2.prepare("UPDATE agent_presence SET agent = ? WHERE agent = ?").run(newName, oldName);
3548
+ throw new Error(`Agent "${normalizedNew}" already exists`);
3549
+ db2.prepare("UPDATE agent_presence SET agent = ? WHERE LOWER(agent) = ?").run(normalizedNew, normalizedOld);
3535
3550
  return true;
3536
3551
  }
3537
3552
  export {
@@ -3562,6 +3577,7 @@ export {
3562
3577
  leaveSpace,
3563
3578
  joinSpace,
3564
3579
  isSpaceMember,
3580
+ isAgentConflict,
3565
3581
  heartbeat,
3566
3582
  getUnreadBlockers,
3567
3583
  getThreadReplies,
@@ -14,6 +14,10 @@ export declare function resolveIdentity(explicit?: string): string;
14
14
  * Throws if no identity is set via flag or env.
15
15
  */
16
16
  export declare function requireIdentity(explicit?: string): string;
17
+ /**
18
+ * Update the cached auto name after a successful rename.
19
+ */
20
+ export declare function updateCachedAutoName(newName: string): void;
17
21
  /**
18
22
  * Reset the cached auto name (for testing).
19
23
  */
@@ -1,4 +1,5 @@
1
1
  import type { AgentPresence, AgentConflictError, RegisterAgentResult } from "../types.js";
2
+ export declare function isAgentConflict(result: RegisterAgentResult | AgentConflictError): result is AgentConflictError;
2
3
  export declare function registerAgent(name: string, sessionId: string, role?: string): RegisterAgentResult | AgentConflictError;
3
4
  export declare function heartbeat(agent: string, status?: string, metadata?: Record<string, unknown>, sessionId?: string): void;
4
5
  export declare function getPresence(agent: string): AgentPresence | null;
package/dist/types.d.ts CHANGED
@@ -123,10 +123,15 @@ export interface AgentPresence {
123
123
  metadata: Record<string, unknown> | null;
124
124
  }
125
125
  export interface AgentConflictError {
126
+ conflict: true;
126
127
  error: "agent_conflict";
127
128
  message: string;
129
+ existing_id: string;
130
+ existing_name: string;
128
131
  existing_session_id: string | null;
129
132
  last_seen_at: string;
133
+ session_hint: string | null;
134
+ working_dir: string | null;
130
135
  }
131
136
  export interface RegisterAgentResult {
132
137
  agent: AgentPresence;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/conversations",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "Real-time CLI messaging for AI agents",
5
5
  "type": "module",
6
6
  "bin": {