@sleep2agi/commhub-server 0.5.0-preview.35 → 0.5.0-preview.37

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/auth.ts +8 -15
  3. package/src/tools.ts +13 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/commhub-server",
3
- "version": "0.5.0-preview.35",
3
+ "version": "0.5.0-preview.37",
4
4
  "description": "CommHub Server \u2014 AI Agent communication hub with MCP protocol, multi-network isolation, user auth, and 18 MCP tools.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/auth.ts CHANGED
@@ -85,22 +85,15 @@ export function login(username: string, password: string): AuthResult {
85
85
  if (!user) return { ok: false, error: "invalid username or password" };
86
86
  if (user.password_hash !== hashPassword(password)) return { ok: false, error: "invalid username or password" };
87
87
 
88
- // Generate/rotate user token (utok_, not bound to network)
89
- let userTokenRow = db.get<any>(
90
- "SELECT token_id FROM api_tokens WHERE user_id = ?1 AND scope = 'user' ORDER BY created_at DESC LIMIT 1",
91
- user.user_id);
92
-
88
+ // Issue a NEW user token do NOT rotate/invalidate existing ones. Each
89
+ // login (cli, dashboard, second machine) gets its own row so they don't
90
+ // kick each other out of session. Tokens can be revoked via /api/auth/tokens.
93
91
  const userToken = generateUserToken();
94
- if (userTokenRow) {
95
- db.run("UPDATE api_tokens SET token_hash = ?1, last_used_at = datetime('now') WHERE token_id = ?2",
96
- [hashToken(userToken), userTokenRow.token_id]);
97
- } else {
98
- const tokenId = generateId("tok");
99
- db.run(
100
- "INSERT INTO api_tokens (token_id, token_hash, user_id, network_id, name, scope) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
101
- [tokenId, hashToken(userToken), user.user_id, null, "user-login", "user"]
102
- );
103
- }
92
+ const tokenId = generateId("tok");
93
+ db.run(
94
+ "INSERT INTO api_tokens (token_id, token_hash, user_id, network_id, name, scope) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
95
+ [tokenId, hashToken(userToken), user.user_id, null, "user-login", "user"]
96
+ );
104
97
 
105
98
  // Find default network
106
99
  const defaultNet = db.get<any>(
package/src/tools.ts CHANGED
@@ -428,12 +428,18 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
428
428
 
429
429
  const session = scopedSessionStatus(alias, effectiveNetId);
430
430
 
431
- // SSE push by alias
431
+ // SSE push by alias.
432
+ // The SSE channel is keyed by alias (subscribers connected to /events/<alias>),
433
+ // not by network_id. Earlier we gated the push on a network-scoped session
434
+ // lookup, which silently dropped pushes whenever an agent registered with
435
+ // network_id=null but the sender supplied an explicit network_id (the
436
+ // exact mismatch hit by Dashboard tasks). Push unconditionally; the
437
+ // subscriber's own auth (ntok_) constrains who can listen.
432
438
  const pendingParams: any[] = [alias];
433
439
  let pendingSql = "SELECT COUNT(*) as cnt FROM inbox WHERE session_name = ?1 AND acked = 0";
434
440
  pendingSql = addScope(pendingSql, pendingParams, effectiveNetId);
435
441
  const pending = db.get<{ cnt: number }>(pendingSql, ...pendingParams);
436
- if (session) pushEvent(alias, { type: "new_task", inbox_count: pending?.cnt ?? 1, priority, from: from_session });
442
+ pushEvent(alias, { type: "new_task", inbox_count: pending?.cnt ?? 1, priority, from: from_session });
437
443
 
438
444
  return {
439
445
  content: [
@@ -471,7 +477,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
471
477
 
472
478
  const session = scopedSessionStatus(alias, effectiveNetId);
473
479
 
474
- if (session) pushEvent(alias, { type: "new_message", message, from: from_session, message_id: id });
480
+ pushEvent(alias, { type: "new_message", message, from: from_session, message_id: id });
475
481
 
476
482
  return {
477
483
  content: [
@@ -531,7 +537,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
531
537
  if (replyLogged && in_reply_to) logTaskEvent(in_reply_to, null, replyStatus, from_session, text.slice(0, 200));
532
538
 
533
539
  const session = scopedSessionStatus(alias, effectiveNetId);
534
- if (session) pushEvent(alias, { type: "new_reply", from: from_session, message_id: id, in_reply_to, status: replyStatus });
540
+ pushEvent(alias, { type: "new_reply", from: from_session, message_id: id, in_reply_to, status: replyStatus });
535
541
 
536
542
  return {
537
543
  content: [{
@@ -607,10 +613,8 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
607
613
  );
608
614
  });
609
615
  logTaskEvent(task_id, task.status, "delivered", from_session, "retry");
610
- // SSE push
611
- if (scopedSessionStatus(task.to_name, effectiveNetId ?? task.network_id)) {
612
- pushEvent(task.to_name, { type: "new_task", inbox_count: 1, priority: task.priority, from: from_session });
613
- }
616
+ // SSE push (unconditional — channel is keyed by alias, not network)
617
+ pushEvent(task.to_name, { type: "new_task", inbox_count: 1, priority: task.priority, from: from_session });
614
618
  return {
615
619
  content: [{ type: "text" as const, text: JSON.stringify({ ok: true, task_id, retried_to: task.to_name }) }],
616
620
  };
@@ -749,9 +753,7 @@ export function registerTools(server: McpServer, clientIP?: string, enforceNetwo
749
753
  [newInboxId, new_alias, task.priority, task.content, from_session, effectiveNetId ?? task.network_id ?? null]);
750
754
  });
751
755
  logTaskEvent(task_id, task.status, "delivered", from_session, `reassign: ${oldAlias} → ${new_alias}`);
752
- if (scopedSessionStatus(new_alias, effectiveNetId ?? task.network_id)) {
753
- pushEvent(new_alias, { type: "new_task", inbox_count: 1, priority: task.priority, from: from_session });
754
- }
756
+ pushEvent(new_alias, { type: "new_task", inbox_count: 1, priority: task.priority, from: from_session });
755
757
  return { content: [{ type: "text" as const, text: JSON.stringify({ ok: true, task_id, reassigned_from: oldAlias, reassigned_to: new_alias }) }] };
756
758
  }
757
759
  );