@sleep2agi/commhub-server 0.5.0-preview.1 → 0.5.0-preview.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/commhub-server",
3
- "version": "0.5.0-preview.1",
3
+ "version": "0.5.0-preview.2",
4
4
  "description": "CommHub MCP Server — AI Agent communication hub with SSE push, MCP protocol, and REST API",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/db.ts CHANGED
@@ -115,6 +115,26 @@ db.exec(`
115
115
  CREATE INDEX IF NOT EXISTS idx_tasks_created ON tasks(created_at);
116
116
  `);
117
117
 
118
+ // nodes table (V2 Sprint 2) — persistent node identity, separate from runtime sessions
119
+ db.exec(`
120
+ CREATE TABLE IF NOT EXISTS nodes (
121
+ node_id TEXT PRIMARY KEY,
122
+ node_name TEXT NOT NULL,
123
+ alias TEXT,
124
+ runtime TEXT,
125
+ model TEXT,
126
+ config_path TEXT,
127
+ channels TEXT,
128
+ server TEXT,
129
+ hostname TEXT,
130
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
131
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
132
+ );
133
+
134
+ CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(node_name);
135
+ CREATE INDEX IF NOT EXISTS idx_nodes_alias ON nodes(alias);
136
+ `);
137
+
118
138
  // Helpers
119
139
  export function uuidv4(): string {
120
140
  return crypto.randomUUID();
package/src/index.ts CHANGED
@@ -281,6 +281,19 @@ Bun.serve({
281
281
  return withCors(req, Response.json({ ok: true, messages: rows }));
282
282
  }
283
283
 
284
+ // ── REST: nodes table (V2 Sprint 2) ──
285
+ if (url.pathname === "/api/nodes") {
286
+ const nodeId = url.searchParams.get("node_id");
287
+ const alias = url.searchParams.get("alias");
288
+ let sql = "SELECT * FROM nodes WHERE 1=1";
289
+ const params: any[] = [];
290
+ if (nodeId) { sql += ` AND node_id = ?${params.length + 1}`; params.push(nodeId); }
291
+ if (alias) { sql += ` AND alias = ?${params.length + 1}`; params.push(alias); }
292
+ sql += " ORDER BY updated_at DESC";
293
+ const rows = db.query(sql).all(...params);
294
+ return withCors(req, Response.json({ ok: true, nodes: rows, count: rows.length }));
295
+ }
296
+
284
297
  // ── REST: tasks table (V2) ──
285
298
  if (url.pathname === "/api/tasks") {
286
299
  const taskId = url.searchParams.get("task_id");
package/src/tools.ts CHANGED
@@ -34,8 +34,9 @@ export function registerTools(server: McpServer, clientIP?: string) {
34
34
  session_id: z.string().max(200).optional().describe("Runtime session/thread ID"),
35
35
  config_path: z.string().max(1000).optional().describe("Config file path"),
36
36
  channels: z.string().max(2000).optional().describe("JSON array of channels"),
37
+ model: z.string().max(200).optional().describe("AI model name"),
37
38
  },
38
- async ({ resume_id, alias, status, task, output, score, progress, server: srv, hostname: hn, agent: ag, project_dir: pd, version: ver, tmux_name: tmux, node_id, session_id, config_path, channels }) => {
39
+ async ({ resume_id, alias, status, task, output, score, progress, server: srv, hostname: hn, agent: ag, project_dir: pd, version: ver, tmux_name: tmux, node_id, session_id, config_path, channels, model: mdl }) => {
39
40
  console.log(`[${ts()}] ${alias} (${resume_id.slice(0, 8)}) → report_status: ${status}${task ? " | " + task.slice(0, 60) : ""}`);
40
41
  const trimmedOutput = output?.slice(0, 4000);
41
42
 
@@ -84,6 +85,29 @@ export function registerTools(server: McpServer, clientIP?: string) {
84
85
  } catch {}
85
86
  }
86
87
 
88
+ // V2: upsert nodes table for persistent node identity
89
+ if (node_id) {
90
+ try {
91
+ // Extract runtime from agent field (e.g., "agent-node:codex" → "codex-sdk")
92
+ const nodeRuntime = ag?.includes(":") ? ag.split(":")[1] + "-sdk" : ag ?? null;
93
+ db.run(
94
+ `INSERT INTO nodes (node_id, node_name, alias, runtime, model, config_path, channels, server, hostname, updated_at)
95
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, datetime('now'))
96
+ ON CONFLICT(node_id) DO UPDATE SET
97
+ node_name = COALESCE(?2, nodes.node_name),
98
+ alias = COALESCE(?3, nodes.alias),
99
+ runtime = COALESCE(?4, nodes.runtime),
100
+ model = COALESCE(?5, nodes.model),
101
+ config_path = COALESCE(?6, nodes.config_path),
102
+ channels = COALESCE(?7, nodes.channels),
103
+ server = COALESCE(?8, nodes.server),
104
+ hostname = COALESCE(?9, nodes.hostname),
105
+ updated_at = datetime('now')`,
106
+ [node_id, alias, alias, nodeRuntime, mdl ?? null, config_path ?? null, channels ?? null, srv ?? null, hn ?? null]
107
+ );
108
+ } catch {}
109
+ }
110
+
87
111
  // inbox uses alias for routing
88
112
  const row = db.query<{ cnt: number }, [string]>(
89
113
  "SELECT COUNT(*) as cnt FROM inbox WHERE session_name = ?1 AND acked = 0"