sbuilder-mcp 0.46.0 → 0.46.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 +10 -0
- package/CHANGELOG.vi.md +10 -0
- package/dist/tools/page.js +67 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,16 @@ 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.46.2] - 2026-09-14
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- A page save that was refused for a reason it did not cause used to still apply and publish its patches locally before the refusal surfaced, so the next successful save on that page silently committed the earlier refused edit as well; a save is now refused before anything is applied whenever it would leave the page still unstorable, while a save that repairs the page's pre-existing problems still goes through.
|
|
13
|
+
|
|
14
|
+
## [0.46.1] - 2026-09-14
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- sb_remove's dry run now reports removing as an actual node count instead of the total patch count, which was off by one in the ordinary case because deleting a subtree also emits one extra patch to unlink it from its parent's child list; the raw patch count is still available under the new patches field.
|
|
18
|
+
|
|
9
19
|
## [0.46.0] - 2026-09-14
|
|
10
20
|
|
|
11
21
|
### Added
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -6,6 +6,16 @@ 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.46.2] - 2026-09-14
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Một lần lưu trang bị từ chối vì lý do nó không gây ra trước đây vẫn áp dụng và publish các patch cục bộ trước khi việc từ chối được báo, nên lần lưu thành công tiếp theo trên trang đó âm thầm gộp luôn cả chỉnh sửa đã bị từ chối trước đó; giờ đây việc lưu bị từ chối trước khi áp dụng bất cứ gì mỗi khi nó khiến trang vẫn không thể lưu được, còn một lần lưu khắc phục được vấn đề đã tồn tại sẵn của trang thì vẫn được thực hiện bình thường.
|
|
13
|
+
|
|
14
|
+
## [0.46.1] - 2026-09-14
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Dry run của sb_remove giờ báo removing là số node thực sự thay vì tổng số patch, vốn bị lệch một do việc xoá một subtree còn phát sinh thêm một patch để gỡ nó khỏi danh sách con của node cha; tổng số patch thô vẫn có sẵn qua trường patches mới.
|
|
18
|
+
|
|
9
19
|
## [0.46.0] - 2026-09-14
|
|
10
20
|
|
|
11
21
|
### Added
|
package/dist/tools/page.js
CHANGED
|
@@ -127,12 +127,35 @@ export class PageSession {
|
|
|
127
127
|
async applyAndSave(patches) {
|
|
128
128
|
const d = this.current();
|
|
129
129
|
const before = new Set(validateForSave(d));
|
|
130
|
-
const
|
|
130
|
+
const after = validateForSave(d.preview(patches));
|
|
131
|
+
const introduced = after.filter((p) => !before.has(p));
|
|
131
132
|
if (introduced.length > 0) {
|
|
132
133
|
throw new Error(`sbuilder: refusing to save — ${introduced.join(' ')}`);
|
|
133
134
|
}
|
|
135
|
+
// AND REFUSE BEFORE APPLYING WHEN THE PAGE STILL CANNOT BE STORED.
|
|
136
|
+
//
|
|
137
|
+
// This used to apply, publish, and only then let `save()` discover the
|
|
138
|
+
// inherited damage — which left the patches in the draft AND on the wire,
|
|
139
|
+
// so the next save that did pass committed an edit the caller had been told
|
|
140
|
+
// was refused. MEASURED on a live page: removing an empty section was
|
|
141
|
+
// refused over four orphans it had nothing to do with, and the next
|
|
142
|
+
// successful remove then stored a document missing FIVE nodes — the four it
|
|
143
|
+
// was asked for, plus the section whose removal had been refused.
|
|
144
|
+
//
|
|
145
|
+
// `6980ebb` named this exact defect ("a refused write left its patches in
|
|
146
|
+
// the draft and blamed the next command") and closed half of it: it stopped
|
|
147
|
+
// the pre-check refusing INHERITED damage, and left the inherited damage
|
|
148
|
+
// being discovered one step too late.
|
|
149
|
+
//
|
|
150
|
+
// An edit that REPAIRS the page still passes, which is what keeps a broken
|
|
151
|
+
// page editable: `after` is the state this write would leave behind, so a
|
|
152
|
+
// write that clears the damage leaves it empty and goes through.
|
|
153
|
+
if (after.length > 0) {
|
|
154
|
+
throw new Error(`sbuilder: refusing to save — this page already cannot be stored, and this write does ` +
|
|
155
|
+
`not repair it, so nothing was applied: ${after.join(' ')}`);
|
|
156
|
+
}
|
|
134
157
|
this.applyAndPublish(patches);
|
|
135
|
-
await this.save();
|
|
158
|
+
await this.save(before);
|
|
136
159
|
}
|
|
137
160
|
applyRemote(patches) {
|
|
138
161
|
this.doc?.apply(patches);
|
|
@@ -182,7 +205,7 @@ export class PageSession {
|
|
|
182
205
|
* autosave later means the agent has spent the interval editing a tree nobody
|
|
183
206
|
* will ever store.
|
|
184
207
|
*/
|
|
185
|
-
async save() {
|
|
208
|
+
async save(inherited = new Set()) {
|
|
186
209
|
if (this.stale) {
|
|
187
210
|
// THE YIELD RULE. The room moved in a way this client cannot reconcile, so
|
|
188
211
|
// it must not write its copy over whatever is there now. Re-pull, and make
|
|
@@ -197,7 +220,28 @@ export class PageSession {
|
|
|
197
220
|
const d = this.current();
|
|
198
221
|
if (d.rev === this.savedRev)
|
|
199
222
|
return;
|
|
200
|
-
|
|
223
|
+
// THE SAME BASELINE `applyAndSave` USES, and the two gates disagreeing is
|
|
224
|
+
// precisely the defect `6980ebb` named and half-closed.
|
|
225
|
+
//
|
|
226
|
+
// That commit — "a refused write left its patches in the draft and blamed
|
|
227
|
+
// the next command" — taught `applyAndSave` to refuse only what an edit
|
|
228
|
+
// INTRODUCES, so a page that arrived broken can still be edited. It left
|
|
229
|
+
// this gate re-deriving the whole list from scratch, so the order became:
|
|
230
|
+
// applyAndSave passes, `applyAndPublish` APPLIES THE PATCHES LOCALLY, and
|
|
231
|
+
// then this throws over a problem that was already there. The patches stay
|
|
232
|
+
// in the draft, and the next save that does pass commits them.
|
|
233
|
+
//
|
|
234
|
+
// MEASURED ON A LIVE PAGE. Removing an empty section was refused here over
|
|
235
|
+
// four orphans it had nothing to do with; the next successful remove then
|
|
236
|
+
// saved a document missing FIVE nodes — the four it was asked for and the
|
|
237
|
+
// section whose removal had been refused. `rev: 3` after one command is the
|
|
238
|
+
// same fact from the other side.
|
|
239
|
+
//
|
|
240
|
+
// Rolling back after a failed save was the other candidate and is worse:
|
|
241
|
+
// `applyAndPublish` has already broadcast the patches over the live socket,
|
|
242
|
+
// so a local rollback would leave every watching editor showing an edit
|
|
243
|
+
// this session no longer has.
|
|
244
|
+
const problems = validateForSave(d).filter((p) => !inherited.has(p));
|
|
201
245
|
if (problems.length > 0) {
|
|
202
246
|
throw new Error(`sbuilder: refusing to save — ${problems.join(' ')}`);
|
|
203
247
|
}
|
|
@@ -684,8 +728,26 @@ export function registerPageTools(server, ctx) {
|
|
|
684
728
|
}, async ({ id, dry_run }) => {
|
|
685
729
|
const d = session.current();
|
|
686
730
|
const patches = removeNode(d, id);
|
|
731
|
+
// NODES, NOT PATCHES — this reported `patches.length` and the field is
|
|
732
|
+
// called `removing` on a tool whose description is "Remove a node and its
|
|
733
|
+
// whole subtree", so every caller reads it as a node count.
|
|
734
|
+
//
|
|
735
|
+
// It is off by one in the ORDINARY case and exact in the rare one, which
|
|
736
|
+
// is the worst arrangement available: `removeNode` emits one `unset` per
|
|
737
|
+
// doomed node PLUS one `remove` that takes the id out of its parent's
|
|
738
|
+
// child list — and that second patch exists only when the parent is still
|
|
739
|
+
// in the document. A childless section under ROOT therefore reported 2,
|
|
740
|
+
// and a four-node subtree whose parent had already been deleted reported
|
|
741
|
+
// 4 and was right. Both numbers were believed here: the 2 was read as
|
|
742
|
+
// evidence of a hidden node attached by `parent` alone, and a page-source
|
|
743
|
+
// dump then proved no such node exists.
|
|
744
|
+
//
|
|
745
|
+
// The doomed set is exactly the `unset` patches that name a node
|
|
746
|
+
// directly; the parent-list patch is an `insert`/`remove` four segments
|
|
747
|
+
// deep, so length is what separates them without re-deriving the walk.
|
|
748
|
+
const nodes = patches.filter((p) => p.op === 'unset' && p.path.length === 2).length;
|
|
687
749
|
if (dry_run !== false)
|
|
688
|
-
return text({ dry_run: true, removing: patches.length });
|
|
750
|
+
return text({ dry_run: true, removing: nodes, patches: patches.length });
|
|
689
751
|
await session.applyAndSave(patches);
|
|
690
752
|
return text({ removed: id, rev: d.rev });
|
|
691
753
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbuilder-mcp",
|
|
3
|
-
"version": "0.46.
|
|
3
|
+
"version": "0.46.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",
|