@sna-sdk/core 0.7.1 → 0.7.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.
@@ -253,7 +253,7 @@ function createAgentRoutes(sessionManager) {
253
253
  } else {
254
254
  cursor = session.eventCounter;
255
255
  }
256
- while (queue.length > 0 && queue[0].cursor <= cursor) queue.shift();
256
+ while (queue.length > 0 && queue[0].cursor !== -1 && queue[0].cursor <= cursor) queue.shift();
257
257
  while (!signal.aborted) {
258
258
  if (queue.length === 0) {
259
259
  await Promise.race([
@@ -267,7 +267,11 @@ function createAgentRoutes(sessionManager) {
267
267
  if (queue.length > 0) {
268
268
  while (queue.length > 0) {
269
269
  const item = queue.shift();
270
- await stream.writeSSE({ id: String(item.cursor), data: JSON.stringify(item.event) });
270
+ if (item.cursor === -1) {
271
+ await stream.writeSSE({ data: JSON.stringify(item.event) });
272
+ } else {
273
+ await stream.writeSSE({ id: String(item.cursor), data: JSON.stringify(item.event) });
274
+ }
271
275
  }
272
276
  } else {
273
277
  await stream.writeSSE({ data: "" });
@@ -108,6 +108,12 @@ declare class SessionManager {
108
108
  /** Broadcast a skill event to all subscribers (called after DB insert). */
109
109
  broadcastSkillEvent(event: Record<string, unknown>): void;
110
110
  /** Push a synthetic event into a session's event stream (for user message broadcast). */
111
+ /**
112
+ * Push an externally-persisted event into the session.
113
+ * The caller is responsible for DB persistence — this method only updates
114
+ * the in-memory counter/buffer and notifies listeners.
115
+ * eventCounter increments to stay in sync with the DB row count.
116
+ */
111
117
  pushEvent(sessionId: string, event: AgentEvent): void;
112
118
  /** Subscribe to permission request notifications. Returns unsubscribe function. */
113
119
  onPermissionRequest(cb: (sessionId: string, request: Record<string, unknown>, createdAt: number) => void): () => void;
@@ -165,6 +171,7 @@ declare class SessionManager {
165
171
  touch(id: string): void;
166
172
  /** Persist an agent event to chat_messages. */
167
173
  private getMessageStats;
174
+ /** Persist an agent event to chat_messages. Returns true if a row was inserted. */
168
175
  private persistEvent;
169
176
  /** Kill all sessions. Used during shutdown. */
170
177
  killAll(): void;
@@ -143,22 +143,27 @@ class SessionManager {
143
143
  }
144
144
  this.setSessionState(sessionId, session, "waiting");
145
145
  }
146
- if (e.type !== "assistant_delta") {
147
- session.eventBuffer.push(e);
148
- if (session.eventBuffer.length > MAX_EVENT_BUFFER) {
149
- session.eventBuffer.splice(0, session.eventBuffer.length - MAX_EVENT_BUFFER);
150
- }
151
- }
152
- session.eventCounter++;
153
146
  if (e.type === "thinking" || e.type === "tool_use" || e.type === "assistant_delta") {
154
147
  this.setSessionState(sessionId, session, "processing");
155
148
  } else if (e.type === "complete" || e.type === "error" || e.type === "interrupted") {
156
149
  this.setSessionState(sessionId, session, "waiting");
157
150
  }
158
- this.persistEvent(sessionId, e);
159
- const listeners = this.eventListeners.get(sessionId);
160
- if (listeners) {
161
- for (const cb of listeners) cb(session.eventCounter, e);
151
+ const persisted = this.persistEvent(sessionId, e);
152
+ if (persisted) {
153
+ session.eventCounter++;
154
+ session.eventBuffer.push(e);
155
+ if (session.eventBuffer.length > MAX_EVENT_BUFFER) {
156
+ session.eventBuffer.splice(0, session.eventBuffer.length - MAX_EVENT_BUFFER);
157
+ }
158
+ const listeners = this.eventListeners.get(sessionId);
159
+ if (listeners) {
160
+ for (const cb of listeners) cb(session.eventCounter, e);
161
+ }
162
+ } else if (e.type === "assistant_delta") {
163
+ const listeners = this.eventListeners.get(sessionId);
164
+ if (listeners) {
165
+ for (const cb of listeners) cb(-1, e);
166
+ }
162
167
  }
163
168
  });
164
169
  proc.on("exit", (code) => {
@@ -196,11 +201,17 @@ class SessionManager {
196
201
  for (const cb of this.skillEventListeners) cb(event);
197
202
  }
198
203
  /** Push a synthetic event into a session's event stream (for user message broadcast). */
204
+ /**
205
+ * Push an externally-persisted event into the session.
206
+ * The caller is responsible for DB persistence — this method only updates
207
+ * the in-memory counter/buffer and notifies listeners.
208
+ * eventCounter increments to stay in sync with the DB row count.
209
+ */
199
210
  pushEvent(sessionId, event) {
200
211
  const session = this.sessions.get(sessionId);
201
212
  if (!session) return;
202
- session.eventBuffer.push(event);
203
213
  session.eventCounter++;
214
+ session.eventBuffer.push(event);
204
215
  if (session.eventBuffer.length > MAX_EVENT_BUFFER) {
205
216
  session.eventBuffer.splice(0, session.eventBuffer.length - MAX_EVENT_BUFFER);
206
217
  }
@@ -425,6 +436,7 @@ class SessionManager {
425
436
  return { messageCount: 0, lastMessage: null };
426
437
  }
427
438
  }
439
+ /** Persist an agent event to chat_messages. Returns true if a row was inserted. */
428
440
  persistEvent(sessionId, e) {
429
441
  try {
430
442
  const db = getDb();
@@ -432,29 +444,34 @@ class SessionManager {
432
444
  case "assistant":
433
445
  if (e.message) {
434
446
  db.prepare(`INSERT INTO chat_messages (session_id, role, content) VALUES (?, 'assistant', ?)`).run(sessionId, e.message);
447
+ return true;
435
448
  }
436
- break;
449
+ return false;
437
450
  case "thinking":
438
451
  if (e.message) {
439
452
  db.prepare(`INSERT INTO chat_messages (session_id, role, content) VALUES (?, 'thinking', ?)`).run(sessionId, e.message);
453
+ return true;
440
454
  }
441
- break;
455
+ return false;
442
456
  case "tool_use": {
443
457
  const toolName = e.data?.toolName ?? e.message ?? "tool";
444
458
  db.prepare(`INSERT INTO chat_messages (session_id, role, content, meta) VALUES (?, 'tool', ?, ?)`).run(sessionId, toolName, JSON.stringify(e.data ?? {}));
445
- break;
459
+ return true;
446
460
  }
447
461
  case "tool_result":
448
462
  db.prepare(`INSERT INTO chat_messages (session_id, role, content, meta) VALUES (?, 'tool_result', ?, ?)`).run(sessionId, e.message ?? "", JSON.stringify(e.data ?? {}));
449
- break;
463
+ return true;
450
464
  case "complete":
451
465
  db.prepare(`INSERT INTO chat_messages (session_id, role, content, meta) VALUES (?, 'status', '', ?)`).run(sessionId, JSON.stringify({ status: "complete", ...e.data }));
452
- break;
466
+ return true;
453
467
  case "error":
454
468
  db.prepare(`INSERT INTO chat_messages (session_id, role, content, meta) VALUES (?, 'error', ?, ?)`).run(sessionId, e.message ?? "Error", JSON.stringify({ status: "error" }));
455
- break;
469
+ return true;
470
+ default:
471
+ return false;
456
472
  }
457
473
  } catch {
474
+ return false;
458
475
  }
459
476
  }
460
477
  /** Kill all sessions. Used during shutdown. */
@@ -1115,7 +1115,7 @@ function createAgentRoutes(sessionManager2) {
1115
1115
  } else {
1116
1116
  cursor = session.eventCounter;
1117
1117
  }
1118
- while (queue.length > 0 && queue[0].cursor <= cursor) queue.shift();
1118
+ while (queue.length > 0 && queue[0].cursor !== -1 && queue[0].cursor <= cursor) queue.shift();
1119
1119
  while (!signal.aborted) {
1120
1120
  if (queue.length === 0) {
1121
1121
  await Promise.race([
@@ -1129,7 +1129,11 @@ function createAgentRoutes(sessionManager2) {
1129
1129
  if (queue.length > 0) {
1130
1130
  while (queue.length > 0) {
1131
1131
  const item = queue.shift();
1132
- await stream.writeSSE({ id: String(item.cursor), data: JSON.stringify(item.event) });
1132
+ if (item.cursor === -1) {
1133
+ await stream.writeSSE({ data: JSON.stringify(item.event) });
1134
+ } else {
1135
+ await stream.writeSSE({ id: String(item.cursor), data: JSON.stringify(item.event) });
1136
+ }
1133
1137
  }
1134
1138
  } else {
1135
1139
  await stream.writeSSE({ data: "" });
@@ -1544,22 +1548,27 @@ var SessionManager = class {
1544
1548
  }
1545
1549
  this.setSessionState(sessionId, session, "waiting");
1546
1550
  }
1547
- if (e.type !== "assistant_delta") {
1548
- session.eventBuffer.push(e);
1549
- if (session.eventBuffer.length > MAX_EVENT_BUFFER) {
1550
- session.eventBuffer.splice(0, session.eventBuffer.length - MAX_EVENT_BUFFER);
1551
- }
1552
- }
1553
- session.eventCounter++;
1554
1551
  if (e.type === "thinking" || e.type === "tool_use" || e.type === "assistant_delta") {
1555
1552
  this.setSessionState(sessionId, session, "processing");
1556
1553
  } else if (e.type === "complete" || e.type === "error" || e.type === "interrupted") {
1557
1554
  this.setSessionState(sessionId, session, "waiting");
1558
1555
  }
1559
- this.persistEvent(sessionId, e);
1560
- const listeners = this.eventListeners.get(sessionId);
1561
- if (listeners) {
1562
- for (const cb of listeners) cb(session.eventCounter, e);
1556
+ const persisted = this.persistEvent(sessionId, e);
1557
+ if (persisted) {
1558
+ session.eventCounter++;
1559
+ session.eventBuffer.push(e);
1560
+ if (session.eventBuffer.length > MAX_EVENT_BUFFER) {
1561
+ session.eventBuffer.splice(0, session.eventBuffer.length - MAX_EVENT_BUFFER);
1562
+ }
1563
+ const listeners = this.eventListeners.get(sessionId);
1564
+ if (listeners) {
1565
+ for (const cb of listeners) cb(session.eventCounter, e);
1566
+ }
1567
+ } else if (e.type === "assistant_delta") {
1568
+ const listeners = this.eventListeners.get(sessionId);
1569
+ if (listeners) {
1570
+ for (const cb of listeners) cb(-1, e);
1571
+ }
1563
1572
  }
1564
1573
  });
1565
1574
  proc.on("exit", (code) => {
@@ -1597,11 +1606,17 @@ var SessionManager = class {
1597
1606
  for (const cb of this.skillEventListeners) cb(event);
1598
1607
  }
1599
1608
  /** Push a synthetic event into a session's event stream (for user message broadcast). */
1609
+ /**
1610
+ * Push an externally-persisted event into the session.
1611
+ * The caller is responsible for DB persistence — this method only updates
1612
+ * the in-memory counter/buffer and notifies listeners.
1613
+ * eventCounter increments to stay in sync with the DB row count.
1614
+ */
1600
1615
  pushEvent(sessionId, event) {
1601
1616
  const session = this.sessions.get(sessionId);
1602
1617
  if (!session) return;
1603
- session.eventBuffer.push(event);
1604
1618
  session.eventCounter++;
1619
+ session.eventBuffer.push(event);
1605
1620
  if (session.eventBuffer.length > MAX_EVENT_BUFFER) {
1606
1621
  session.eventBuffer.splice(0, session.eventBuffer.length - MAX_EVENT_BUFFER);
1607
1622
  }
@@ -1826,6 +1841,7 @@ var SessionManager = class {
1826
1841
  return { messageCount: 0, lastMessage: null };
1827
1842
  }
1828
1843
  }
1844
+ /** Persist an agent event to chat_messages. Returns true if a row was inserted. */
1829
1845
  persistEvent(sessionId, e) {
1830
1846
  try {
1831
1847
  const db = getDb();
@@ -1833,29 +1849,34 @@ var SessionManager = class {
1833
1849
  case "assistant":
1834
1850
  if (e.message) {
1835
1851
  db.prepare(`INSERT INTO chat_messages (session_id, role, content) VALUES (?, 'assistant', ?)`).run(sessionId, e.message);
1852
+ return true;
1836
1853
  }
1837
- break;
1854
+ return false;
1838
1855
  case "thinking":
1839
1856
  if (e.message) {
1840
1857
  db.prepare(`INSERT INTO chat_messages (session_id, role, content) VALUES (?, 'thinking', ?)`).run(sessionId, e.message);
1858
+ return true;
1841
1859
  }
1842
- break;
1860
+ return false;
1843
1861
  case "tool_use": {
1844
1862
  const toolName = e.data?.toolName ?? e.message ?? "tool";
1845
1863
  db.prepare(`INSERT INTO chat_messages (session_id, role, content, meta) VALUES (?, 'tool', ?, ?)`).run(sessionId, toolName, JSON.stringify(e.data ?? {}));
1846
- break;
1864
+ return true;
1847
1865
  }
1848
1866
  case "tool_result":
1849
1867
  db.prepare(`INSERT INTO chat_messages (session_id, role, content, meta) VALUES (?, 'tool_result', ?, ?)`).run(sessionId, e.message ?? "", JSON.stringify(e.data ?? {}));
1850
- break;
1868
+ return true;
1851
1869
  case "complete":
1852
1870
  db.prepare(`INSERT INTO chat_messages (session_id, role, content, meta) VALUES (?, 'status', '', ?)`).run(sessionId, JSON.stringify({ status: "complete", ...e.data }));
1853
- break;
1871
+ return true;
1854
1872
  case "error":
1855
1873
  db.prepare(`INSERT INTO chat_messages (session_id, role, content, meta) VALUES (?, 'error', ?, ?)`).run(sessionId, e.message ?? "Error", JSON.stringify({ status: "error" }));
1856
- break;
1874
+ return true;
1875
+ default:
1876
+ return false;
1857
1877
  }
1858
1878
  } catch {
1879
+ return false;
1859
1880
  }
1860
1881
  }
1861
1882
  /** Kill all sessions. Used during shutdown. */
@@ -2326,7 +2347,11 @@ function handleAgentSubscribe(ws, msg, sm, state) {
2326
2347
  }
2327
2348
  }
2328
2349
  const unsub = sm.onSessionEvent(sessionId, (eventCursor, event) => {
2329
- send(ws, { type: "agent.event", session: sessionId, cursor: eventCursor, event });
2350
+ if (eventCursor === -1) {
2351
+ send(ws, { type: "agent.event", session: sessionId, event });
2352
+ } else {
2353
+ send(ws, { type: "agent.event", session: sessionId, cursor: eventCursor, event });
2354
+ }
2330
2355
  });
2331
2356
  state.agentUnsubs.set(sessionId, unsub);
2332
2357
  reply(ws, msg, { cursor });
package/dist/server/ws.js CHANGED
@@ -459,7 +459,11 @@ function handleAgentSubscribe(ws, msg, sm, state) {
459
459
  }
460
460
  }
461
461
  const unsub = sm.onSessionEvent(sessionId, (eventCursor, event) => {
462
- send(ws, { type: "agent.event", session: sessionId, cursor: eventCursor, event });
462
+ if (eventCursor === -1) {
463
+ send(ws, { type: "agent.event", session: sessionId, event });
464
+ } else {
465
+ send(ws, { type: "agent.event", session: sessionId, cursor: eventCursor, event });
466
+ }
463
467
  });
464
468
  state.agentUnsubs.set(sessionId, unsub);
465
469
  reply(ws, msg, { cursor });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sna-sdk/core",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Skills-Native Application runtime — server, providers, session management, database, and CLI",
5
5
  "type": "module",
6
6
  "bin": {