sbuilder-mcp 0.63.1 → 0.63.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/CHANGELOG.md CHANGED
@@ -6,6 +6,11 @@ All notable changes to this project are documented in this file.
6
6
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
7
7
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
+ ## [0.63.2] - 2026-09-24
10
+
11
+ ### Fixed
12
+ - The live-edit socket now holds frames sent before the server's welcome instead of dropping them, so a section added right after `sb_page_open` reaches an editor watching the page instead of leaving it showing "This page is empty" and risking that empty state being autosaved over the agent's work; the held queue is capped at 500 frames so a long reconnect loop cannot grow memory without bound.
13
+
9
14
  ## [0.63.1] - 2026-09-24
10
15
 
11
16
  ### Fixed
package/CHANGELOG.vi.md CHANGED
@@ -6,6 +6,11 @@ Mọi thay đổi đáng chú ý của dự án được ghi lại trong file n
6
6
  Định dạng dựa trên [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
7
7
  và dự án tuân theo [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
+ ## [0.63.2] - 2026-09-24
10
+
11
+ ### Fixed
12
+ - Socket live-edit giờ giữ lại các frame được gửi trước khi server chào (welcome) thay vì loại bỏ chúng, để một section được thêm ngay sau `sb_page_open` đến được với editor đang xem trang thay vì để trang đó hiển thị "This page is empty" và có nguy cơ trạng thái rỗng đó bị autosave đè lên công việc của agent; hàng đợi giữ frame bị giới hạn ở 500 frame để một vòng lặp reconnect kéo dài không thể làm bộ nhớ tăng vô hạn.
13
+
9
14
  ## [0.63.1] - 2026-09-24
10
15
 
11
16
  ### Fixed
@@ -11,6 +11,8 @@ import { agentIdentity } from './identity.js';
11
11
  const OPEN = 1;
12
12
  const RETRY_BASE_MS = 500;
13
13
  const RETRY_MAX_MS = 15_000;
14
+ /** Frames held while not ready. A reconnect loop must not grow memory forever. */
15
+ const QUEUE_MAX = 500;
14
16
  export class RealtimeSocket {
15
17
  url;
16
18
  token;
@@ -20,6 +22,19 @@ export class RealtimeSocket {
20
22
  attempt = 0;
21
23
  handlers = [];
22
24
  timer = null;
25
+ /** The server has welcomed THIS connection — frames sent now are read. */
26
+ ready = false;
27
+ /**
28
+ * Frames sent while not ready, flushed on the welcome.
29
+ *
30
+ * `sb_page_open` starts the connection and returns; the `sb_add` right after
31
+ * it ran before the handshake finished, and a send that DROPPED on a
32
+ * not-open socket lost it without a trace. The editor watching the page never
33
+ * saw the section appear — it showed an empty page, and its next autosave
34
+ * could write that empty page over the agent's work. Same for every edit made
35
+ * during a reconnect.
36
+ */
37
+ queue = [];
23
38
  constructor(url,
24
39
  /**
25
40
  * A GETTER, read per attempt. The access token lives ~15 minutes and
@@ -44,6 +59,7 @@ export class RealtimeSocket {
44
59
  const ws = this.factory(this.url);
45
60
  this.ws = ws;
46
61
  this.attempt += 1;
62
+ this.ready = false;
47
63
  ws.onopen = () => {
48
64
  // Auth is the FIRST message, never a header (a browser cannot set one on a
49
65
  // WebSocket) and never a query parameter (a token in a URL lands in logs).
@@ -92,10 +108,17 @@ export class RealtimeSocket {
92
108
  catch {
93
109
  return; // a malformed frame must never throw into the socket
94
110
  }
111
+ // Ready BEFORE the handlers, flushed AFTER them: the page announcement
112
+ // the welcome triggers goes out directly, ahead of the ops held for it.
113
+ const held = this.ready ? [] : this.queue.splice(0);
114
+ this.ready = true;
95
115
  for (const h of this.handlers)
96
116
  h(parsed);
117
+ for (const f of held)
118
+ ws.send(f);
97
119
  };
98
120
  ws.onclose = () => {
121
+ this.ready = false;
99
122
  if (this.closed)
100
123
  return;
101
124
  const wait = Math.min(RETRY_BASE_MS * 2 ** this.attempt, RETRY_MAX_MS);
@@ -109,12 +132,21 @@ export class RealtimeSocket {
109
132
  send(e) {
110
133
  if (this.closed)
111
134
  return;
112
- if (this.ws?.readyState !== OPEN)
135
+ const frame = JSON.stringify(e);
136
+ if (this.ready && this.ws?.readyState === OPEN) {
137
+ this.ws.send(frame);
113
138
  return;
114
- this.ws.send(JSON.stringify(e));
139
+ }
140
+ if (this.queue.length >= QUEUE_MAX) {
141
+ // ponytail: drops the oldest past the cap; the peer re-pulls on the gap.
142
+ console.error('[sbuilder-mcp] live queue full, dropping the oldest frame');
143
+ this.queue.shift();
144
+ }
145
+ this.queue.push(frame);
115
146
  }
116
147
  close() {
117
148
  this.closed = true;
149
+ this.queue = [];
118
150
  if (this.timer)
119
151
  clearTimeout(this.timer);
120
152
  this.ws?.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sbuilder-mcp",
3
- "version": "0.63.1",
3
+ "version": "0.63.2",
4
4
  "description": "MCP server that designs and operates a Store Builder site — pages, data, theme and publish — through the platform's own API and live-edit protocol.",
5
5
  "mcpName": "io.github.vuluu2k/sbuilder-mcp",
6
6
  "type": "module",