@nullbridge/sdk 1.0.3 → 1.0.4

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/agents.js +72 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nullbridge/sdk",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "NullBridge AI Agent Identity Governance SDK",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
package/src/agents.js CHANGED
@@ -9,34 +9,36 @@ class AgentRegistry {
9
9
  constructor(config, http) {
10
10
  this._config = config;
11
11
  this._http = http;
12
- this._agents = new Map(); // local cache
12
+ this._agents = new Map();
13
13
  }
14
14
 
15
15
  /**
16
16
  * Register a new AI agent with NullBridge.
17
17
  *
18
18
  * @param {object} options
19
+ * @param {string} [options.id] - Stable unique identifier. Auto-generated if omitted.
19
20
  * @param {string} options.name - Human-readable agent name (e.g. 'claims-processor')
20
21
  * @param {string} options.type - Agent type: 'llm' | 'rpa' | 'workflow' | 'custom'
21
22
  * @param {string} [options.model] - Model name (e.g. 'gpt-4o', 'claude-3-5-sonnet')
22
- * @param {string[]} [options.scopes] - What this agent is allowed to do (e.g. ['read:claims', 'write:reports'])
23
- * @param {object} [options.metadata] - Any additional metadata
23
+ * @param {string[]} [options.scopes] - Permission scopes (e.g. ['read:claims', 'write:reports'])
24
+ * @param {object} [options.metadata] - Additional metadata
25
+ * @param {number} [options.heartbeatInterval] - Heartbeat interval in ms (default: 60000)
24
26
  * @returns {Promise<Agent>}
25
27
  */
26
28
  async register(options = {}) {
27
29
  if (!options.name) throw new Error('[NullBridge] agent.name is required');
28
30
  if (!options.type) throw new Error('[NullBridge] agent.type is required');
29
31
 
30
- const agentId = this._generateAgentId(options.name);
32
+ const agentId = options.id || this._generateAgentId(options.name);
31
33
 
32
34
  const payload = {
33
- id: agentId,
34
- name: options.name,
35
- type: options.type,
36
- model: options.model || null,
37
- scopes: options.scopes || [],
38
- metadata: options.metadata || {},
39
- sdk_version: '1.0.0',
35
+ id: agentId,
36
+ name: options.name,
37
+ type: options.type,
38
+ model: options.model || null,
39
+ scopes: options.scopes || [],
40
+ metadata: options.metadata || {},
41
+ sdk_version: '1.0.3',
40
42
  registered_at: Math.floor(Date.now() / 1000),
41
43
  };
42
44
 
@@ -47,13 +49,20 @@ class AgentRegistry {
47
49
  { license_key: this._config.licenseKey, agent: payload }
48
50
  );
49
51
 
50
- const agent = status === 200 || status === 201
52
+ const agent = (status === 200 || status === 201)
51
53
  ? { ...payload, ...body.agent, _registered: true }
52
54
  : { ...payload, _registered: false, _local: true };
53
55
 
54
56
  this._agents.set(agentId, agent);
55
57
  console.info(`[NullBridge] Agent registered: ${options.name} (${agentId})`);
56
- return new Agent(agent, this._config, this._http);
58
+
59
+ const instance = new Agent(agent, this._config, this._http);
60
+
61
+ // Start heartbeat automatically
62
+ const heartbeatMs = options.heartbeatInterval || 60000;
63
+ instance.startHeartbeat(heartbeatMs);
64
+
65
+ return instance;
57
66
 
58
67
  } catch (err) {
59
68
  // API unreachable — register locally and continue
@@ -103,9 +112,10 @@ class AgentRegistry {
103
112
  */
104
113
  class Agent {
105
114
  constructor(data, config, http) {
106
- this._data = data;
107
- this._config = config;
108
- this._http = http;
115
+ this._data = data;
116
+ this._config = config;
117
+ this._http = http;
118
+ this._heartbeatTimer = null;
109
119
 
110
120
  // Expose agent properties
111
121
  this.id = data.id;
@@ -166,9 +176,10 @@ class Agent {
166
176
  }
167
177
 
168
178
  /**
169
- * Deregister this agent — revokes its identity and credentials
179
+ * Deregister this agent — revokes its identity and stops heartbeat
170
180
  */
171
181
  async deregister() {
182
+ this.stopHeartbeat();
172
183
  try {
173
184
  await this._http.post(
174
185
  this._config.apiUrl,
@@ -181,6 +192,50 @@ class Agent {
181
192
  }
182
193
  }
183
194
 
195
+ /**
196
+ * Send a heartbeat to update last_seen in the dashboard.
197
+ * Called automatically by startHeartbeat() — you don't need to call this manually.
198
+ */
199
+ async heartbeat() {
200
+ try {
201
+ await this._http.post(
202
+ this._config.apiUrl,
203
+ `/sdk/agents/${this.id}/heartbeat`,
204
+ { license_key: this._config.licenseKey }
205
+ );
206
+ if (this._config.debug) {
207
+ console.debug(`[NullBridge] Heartbeat sent: ${this.name}`);
208
+ }
209
+ } catch {
210
+ // Silently fail — heartbeat is best-effort
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Start sending periodic heartbeats to keep last_seen current in the dashboard.
216
+ * Called automatically after register(). You only need this if you stopped it manually.
217
+ *
218
+ * @param {number} [intervalMs=60000] - How often to ping in milliseconds
219
+ */
220
+ startHeartbeat(intervalMs = 60000) {
221
+ if (this._heartbeatTimer) return; // already running
222
+ // Send immediately on start
223
+ this.heartbeat();
224
+ this._heartbeatTimer = setInterval(() => this.heartbeat(), intervalMs);
225
+ // Allow Node.js to exit even if heartbeat is still running
226
+ if (this._heartbeatTimer.unref) this._heartbeatTimer.unref();
227
+ }
228
+
229
+ /**
230
+ * Stop sending heartbeats. Called automatically by deregister() and shutdown().
231
+ */
232
+ stopHeartbeat() {
233
+ if (this._heartbeatTimer) {
234
+ clearInterval(this._heartbeatTimer);
235
+ this._heartbeatTimer = null;
236
+ }
237
+ }
238
+
184
239
  toJSON() {
185
240
  return {
186
241
  id: this.id, name: this.name, type: this.type,