@lacneu/wix-openclaw 0.2.2 → 0.4.0
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 +510 -1
- package/README.md +109 -8
- package/dist/config.d.ts +1 -0
- package/dist/config.js +25 -1
- package/dist/config.js.map +1 -1
- package/dist/hooks/approval.d.ts +10 -0
- package/dist/hooks/approval.js +301 -18
- package/dist/hooks/approval.js.map +1 -1
- package/dist/index.d.ts +1586 -8
- package/dist/index.js +63 -19
- package/dist/index.js.map +1 -1
- package/dist/tools/_factory.d.ts +26 -2
- package/dist/tools/_factory.js.map +1 -1
- package/dist/tools/blog.d.ts +11 -11
- package/dist/tools/bookings.d.ts +5 -5
- package/dist/tools/contacts.d.ts +5 -5
- package/dist/tools/data.d.ts +5 -5
- package/dist/tools/design.d.ts +1 -1
- package/dist/tools/events.d.ts +3 -3
- package/dist/tools/faq.d.ts +5 -5
- package/dist/tools/forms.d.ts +2 -2
- package/dist/tools/media.d.ts +2 -2
- package/dist/tools/multilingual.d.ts +2 -2
- package/dist/tools/reviews.d.ts +3 -3
- package/dist/tools/seo.d.ts +438 -0
- package/dist/tools/seo.js +1875 -0
- package/dist/tools/seo.js.map +1 -0
- package/dist/tools/site.d.ts +2 -2
- package/dist/types.d.ts +11 -0
- package/dist/wix-client.js +30 -4
- package/dist/wix-client.js.map +1 -1
- package/openclaw.plugin.json +57 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,514 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2026-08-29
|
|
11
|
+
|
|
12
|
+
Ten more `wix_seo_*` tools, still **off by default**, close the other half of
|
|
13
|
+
the SEO gap: 0.3.x could audit a site's tags and never correct them. Writing
|
|
14
|
+
them is a different destructiveness from deleting a redirect — a tag write
|
|
15
|
+
replaces **the previous value of what it writes** — so the guard is different
|
|
16
|
+
too, and most of this entry is about that.
|
|
17
|
+
|
|
18
|
+
### Added — SEO: writing tags, and the patterns behind them
|
|
19
|
+
|
|
20
|
+
> Same warning as the audit tools: these endpoints are in Developer Preview,
|
|
21
|
+
> Wix states they should not be used on live sites, and **every tool here
|
|
22
|
+
> replaces what it writes**. They register only with `enableSeoTools: true`.
|
|
23
|
+
|
|
24
|
+
Ten tools complete the SEO surface: you could audit tags but not correct them.
|
|
25
|
+
|
|
26
|
+
**Per-item and site-wide tags.** `wix_seo_set_item_tags`,
|
|
27
|
+
`wix_seo_bulk_set_item_tags`, `wix_seo_reset_item_tags` and
|
|
28
|
+
`wix_seo_set_site_tags`.
|
|
29
|
+
|
|
30
|
+
**Patterns.** `wix_seo_list_patterns`, `wix_seo_get_pattern`,
|
|
31
|
+
`wix_seo_list_pattern_variables` (read-only) plus `wix_seo_set_pattern`,
|
|
32
|
+
`wix_seo_create_pattern` and `wix_seo_reset_pattern`.
|
|
33
|
+
|
|
34
|
+
### The destructiveness is a different shape, so the guard is too
|
|
35
|
+
|
|
36
|
+
A redirect delete destroys another row — it can be listed and named. A tag write
|
|
37
|
+
destroys **the previous value of what it writes**: `tags` replaces the item's set
|
|
38
|
+
in full, so a caller that sets a title wipes the description it never mentioned,
|
|
39
|
+
gets exactly what it asked for, and loses something it never saw. That is this
|
|
40
|
+
half's "redirects do not chain".
|
|
41
|
+
|
|
42
|
+
The pre-flight therefore reports what **disappears** — the tags held today whose
|
|
43
|
+
slot the incoming set does not fill — never an echo of the input. A caller
|
|
44
|
+
reading its own request back learns nothing. Nothing is lost, nothing is
|
|
45
|
+
refused: the ceremony exists for the unannounced loss, not for its own sake.
|
|
46
|
+
|
|
47
|
+
A pattern write earns the heaviest pre-flight in the plugin. It re-renders
|
|
48
|
+
**every page of its type**, it has no revision, and the Wix dashboard writes to
|
|
49
|
+
the same object — last write wins. So the refusal reports the page count
|
|
50
|
+
downstream alongside the templates, and the token is bound to the pattern read
|
|
51
|
+
immediately before — **and to that page count**, because the radius is part of
|
|
52
|
+
what was accepted: a token minted while a type held twelve pages does not
|
|
53
|
+
authorise the same write once it holds four hundred.
|
|
54
|
+
|
|
55
|
+
Every refusal in this family carries the **value** of what it names, not only
|
|
56
|
+
its slot.
|
|
57
|
+
Editing `{{title}}` into `{{title}} — Ataraxis` loses no tag, so a report by
|
|
58
|
+
slot alone read identically before and after — an operator signing a rewrite of
|
|
59
|
+
every page of a type without seeing either version.
|
|
60
|
+
|
|
61
|
+
### Three contract facts that live in code, not in prose
|
|
62
|
+
|
|
63
|
+
- **`fieldMask` is a comma-separated STRING over REST.** The SDK takes an array,
|
|
64
|
+
which is what a model reaches for. It is built here, never accepted.
|
|
65
|
+
- **`language` is actively dropped.** Tags can only be written for the item's
|
|
66
|
+
primary language, and sending one fails `LANGUAGE_NOT_SUPPORTED` *even when
|
|
67
|
+
the field mask omits it* — so not declaring the field is not enough, TypeBox
|
|
68
|
+
passes undeclared properties through.
|
|
69
|
+
- **Site SEO Tags accepts only `meta`.** Wix rejects `title`, `script` and
|
|
70
|
+
`link` there despite the shared tag shape listing them, and there is no way to
|
|
71
|
+
set site-wide structured data through any Wix API — a per-page `script` tag is
|
|
72
|
+
the route.
|
|
73
|
+
|
|
74
|
+
`BLOG_POST` bulk writes are capped at 20 entries, not 100, and Wix rejects the
|
|
75
|
+
**whole call** past an item type's limit — a 40-post batch would write nothing —
|
|
76
|
+
so the cap is enforced before the request leaves.
|
|
77
|
+
|
|
78
|
+
### The `publish` split, stated where a model will read it
|
|
79
|
+
|
|
80
|
+
For a static page, `publish: true` updates **only** the published revision. The
|
|
81
|
+
read tools always return the **saved** one, so after such a write they keep
|
|
82
|
+
showing the old values — that is not a failed write, and re-sending will not
|
|
83
|
+
change it. The method's own response is not a read of the published page either.
|
|
84
|
+
A caller following the description literally converges instead of retrying.
|
|
85
|
+
|
|
86
|
+
## [0.3.1] - 2026-08-28
|
|
87
|
+
|
|
88
|
+
**The version that actually reached npm for the 0.3 line.** `v0.3.0` was
|
|
89
|
+
tagged, failed to compile in the release workflow and was never published, so
|
|
90
|
+
`0.3.1` carries the whole 0.3.0 entry below plus the fix that unblocked it.
|
|
91
|
+
|
|
92
|
+
### Fixed
|
|
93
|
+
|
|
94
|
+
- **The build imported a type from a subpath that does not exist across the
|
|
95
|
+
supported range.** `openclaw/plugin-sdk/agent-core` only appeared in
|
|
96
|
+
2026.5.28, while this plugin declares `>=2026.5.0`, so CI failed on the lower
|
|
97
|
+
bound. The shared tool-result type is declared locally instead — verified at
|
|
98
|
+
both ends of the supported range rather than at the version that happened to
|
|
99
|
+
be installed.
|
|
100
|
+
|
|
101
|
+
## [0.3.0] - 2026-08-28
|
|
102
|
+
|
|
103
|
+
Nine `wix_seo_*` tools, **off by default**, close the gap that made this plugin
|
|
104
|
+
able to manage a site's *content* but not to look at its SEO. Turn them on with
|
|
105
|
+
`enableSeoTools: true`.
|
|
106
|
+
|
|
107
|
+
**One fix here affects every existing install, whether or not you want the SEO
|
|
108
|
+
tools:** approval prompts were too long for the gateway to accept, so every
|
|
109
|
+
approval-gated tool was blocked without the operator ever being asked. See
|
|
110
|
+
*approval prompts were too long for the gateway to show at all* below.
|
|
111
|
+
|
|
112
|
+
The three SEO read paths were verified against a live Wix site; the four writing
|
|
113
|
+
paths were deliberately left unexercised. Everything else recorded under this
|
|
114
|
+
version is work on code that had not shipped before — it is written down because
|
|
115
|
+
the reasoning is the point, not because anyone was affected by it.
|
|
116
|
+
|
|
117
|
+
### Added — SEO: auditing a site, and managing its URL redirects
|
|
118
|
+
|
|
119
|
+
> **These tools are OFF by default and must be opted into with
|
|
120
|
+
> `enableSeoTools: true`.** Every Wix endpoint behind them is in Developer
|
|
121
|
+
> Preview: Wix may change them at any time and states they shouldn't be used on
|
|
122
|
+
> live sites. Four of them also write irreversibly the moment they succeed.
|
|
123
|
+
|
|
124
|
+
**Permission.** All nine sit behind a single Wix scope,
|
|
125
|
+
`SCOPE.PROMOTE.MANAGE-SEO` — shown untranslated in the Wix UI, under *All site
|
|
126
|
+
permissions*, between *Social Posts* and *Import orders*. Wix publishes no
|
|
127
|
+
read-only SEO scope, so a key that can audit a site can also delete its
|
|
128
|
+
redirects: the read/write boundary is `enableSeoTools` plus the approvals, not
|
|
129
|
+
the key. The three read paths were verified live against a real site; the four
|
|
130
|
+
writing paths were deliberately left unexercised.
|
|
131
|
+
|
|
132
|
+
Nine tools under `wix_seo_*`, closing the gap that made the plugin able to
|
|
133
|
+
manage a site's *content* but not to look at its SEO.
|
|
134
|
+
|
|
135
|
+
**Audit.** `wix_seo_list_item_tags` is the entry point: pass `STATIC_PAGE` and
|
|
136
|
+
it enumerates the site's pages, each with the tags it sets itself, whether it
|
|
137
|
+
sets any (`hasOverride`), and the tags it is expected to render with — every one
|
|
138
|
+
marked with where it came from (the item, its host page, a customised pattern,
|
|
139
|
+
Wix's own pattern, or the site). Other item types are `BLOG_POST`,
|
|
140
|
+
`STORES_PRODUCT` and `WIX_DATA_PAGE_ITEM-{pageId}`. `wix_seo_get_item_tags` and
|
|
141
|
+
`wix_seo_get_site_tags` read one item and the site-wide tags.
|
|
142
|
+
|
|
143
|
+
**Redirects.** List, get, create, delete, and both bulk forms.
|
|
144
|
+
|
|
145
|
+
### Two limits stated in the tool descriptions, not just the docs
|
|
146
|
+
|
|
147
|
+
*A static page reads as its SAVED revision, never the published one*, and
|
|
148
|
+
`publishStatus` is always `PUBLISH_STATUS_UNSPECIFIED` for one — Wix exposes no
|
|
149
|
+
path to read the published revision back. The description says so, because an
|
|
150
|
+
agent that doesn't know it will report on a draft as though it were the live
|
|
151
|
+
page. `resolvedTags` likewise omits whatever site code and apps add at render
|
|
152
|
+
time, so it is not the page's rendered `<head>`.
|
|
153
|
+
|
|
154
|
+
### Changed — `wix_seo_create_redirect` is approval-gated, like the deletes
|
|
155
|
+
|
|
156
|
+
Creating a redirect is destructive by construction. Redirects do not chain: when
|
|
157
|
+
an existing redirect **starts at** the path a new one points to, that existing
|
|
158
|
+
redirect is deleted so the create can proceed — no flag asked for, and no way
|
|
159
|
+
back. `options.forceReplace` deletes a second one. Gating by verb would have
|
|
160
|
+
left the most damaging call in the set ungated.
|
|
161
|
+
|
|
162
|
+
### Fixed — approval prompts were too long for the gateway to show at all
|
|
163
|
+
|
|
164
|
+
The gateway declares an approval's `title` as 1–80 characters and its
|
|
165
|
+
`description` as 1–256 (`PluginApprovalRequestParamsSchema`). The prompt this
|
|
166
|
+
plugin built embedded a 600-character parameter preview under a paragraph of
|
|
167
|
+
boilerplate, so it could not fit — and an over-long request is not truncated,
|
|
168
|
+
it fails validation. **Every approval-gated tool was affected, not only the new
|
|
169
|
+
ones**: the operator was never asked, and the call was blocked.
|
|
170
|
+
|
|
171
|
+
The description is now BUILT to that budget rather than written and hoped for,
|
|
172
|
+
ordered by what decides a refusal: the irreversible effect first, then the site
|
|
173
|
+
the call actually lands on (the old preview stripped `siteId`, so on a
|
|
174
|
+
multi-site key the operator never saw which tenant), then the parameters that
|
|
175
|
+
matter, then everything else until the budget runs out.
|
|
176
|
+
|
|
177
|
+
A redirect is shown with its SCOPE — exact or group, and its language or `all`
|
|
178
|
+
— because the paths alone made an exact French-only rule and a global group rule
|
|
179
|
+
over a whole branch read identically, while the second can make far more pages
|
|
180
|
+
unreachable. That applies to a bulk create's members too, which had been going
|
|
181
|
+
through the generic JSON path: up to 500 group rules over whole branches read
|
|
182
|
+
the same as 500 exact ones. A batch also reports a count PER SCOPE over every
|
|
183
|
+
member, and its sample leads with the widest rules. Its size and fingerprint are
|
|
184
|
+
their own part of the prompt, so a long scope breakdown can be dropped without
|
|
185
|
+
taking the batch's identity with it — sampling in request order
|
|
186
|
+
put three exact rules on screen while the global group ones sat inside an
|
|
187
|
+
opaque fingerprint. A list reports its size, a few of its members and a stable
|
|
188
|
+
fingerprint of the whole set — reporting only a count made two bulk deletes of completely
|
|
189
|
+
different GUIDs read identically. Long values are shortened in the middle, so
|
|
190
|
+
two paths sharing a prefix stay distinguishable, and a value that still cannot
|
|
191
|
+
fit is skipped rather than ending the rendering, which is how `from` and `to`
|
|
192
|
+
could disappear from a prompt entirely. A test asserts both limits for every
|
|
193
|
+
gated tool, at the largest values Wix itself accepts.
|
|
194
|
+
|
|
195
|
+
### Changed — a redirect create REFUSES rather than silently destroying
|
|
196
|
+
|
|
197
|
+
Creating a redirect deletes an existing one that starts at the path the new one
|
|
198
|
+
points to — redirects do not chain — and `forceReplace` deletes the one holding
|
|
199
|
+
the same `from` path. Neither is reported by the API, and telling the agent to
|
|
200
|
+
check first only made it advice: a direct call, or a model that skipped the
|
|
201
|
+
instruction, still lost a redirect nobody had named.
|
|
202
|
+
|
|
203
|
+
`wix_seo_create_redirect` and `wix_seo_bulk_create_redirects` now list the
|
|
204
|
+
site's redirects themselves and refuse, naming each redirect they would delete
|
|
205
|
+
and why, until the caller repeats those GUIDs back in `acknowledgeDeletions`.
|
|
206
|
+
Trailing-slash and case differences count as the same path, as Wix treats them.
|
|
207
|
+
A create that destroys nothing goes straight through.
|
|
208
|
+
|
|
209
|
+
### Changed — the destructive preflight compares what Wix STORES, and fails closed
|
|
210
|
+
|
|
211
|
+
Three ways the check could be walked around, all closed:
|
|
212
|
+
|
|
213
|
+
- **Language prefixes.** Wix stores a language-scoped redirect without its
|
|
214
|
+
prefix — a `fr` redirect from `/fr/about` comes back as `/about`. Comparing
|
|
215
|
+
the caller's path against the stored one found nothing, so a language-scoped
|
|
216
|
+
deletion went through unannounced. Paths are now normalised the way Wix
|
|
217
|
+
stores them, against the language of BOTH sides: a global create pointing at
|
|
218
|
+
`/fr/blog` is aiming at the public path of a `fr` redirect stored as `/blog`,
|
|
219
|
+
and normalising only by the candidate's own language (none, there) left the
|
|
220
|
+
two looking unrelated. Scopes that cannot collide are skipped rather than
|
|
221
|
+
flagged.
|
|
222
|
+
- **An unreadable list.** Anything that wasn't a `redirects` array — a 2xx the
|
|
223
|
+
client couldn't parse, a schema change, a proxy's own page — read as "this
|
|
224
|
+
site has no redirects", and the destructive write went out on the strength of
|
|
225
|
+
it. The call now refuses. The one answer a safety check must never invent is
|
|
226
|
+
the reassuring one.
|
|
227
|
+
- **Concurrency.** The check and the write are two requests, so two concurrent
|
|
228
|
+
creates could each read a state without the other's redirect and the second to
|
|
229
|
+
land would delete the first's. Destructive writes are now serialised per
|
|
230
|
+
EFFECTIVE site — keying on the raw argument put an omitted `siteId` and the
|
|
231
|
+
same site named explicitly into two different queues, so the calls the lock
|
|
232
|
+
exists to order ran side by side. A change made in the Wix dashboard between
|
|
233
|
+
the read and the write is still possible — Wix offers no precondition to close
|
|
234
|
+
that — and the tool descriptions say so rather than promising otherwise.
|
|
235
|
+
- **An entry the plugin cannot read.** Validating only that `redirects` was an
|
|
236
|
+
array closed the check at the top and left it open underneath: a preview
|
|
237
|
+
rename to `source` passes an array test, every `from` reads as undefined, no
|
|
238
|
+
conflict is found, and the write goes out on a list nobody understood. Each
|
|
239
|
+
entry is now checked before anything is computed from it.
|
|
240
|
+
|
|
241
|
+
### Changed — an acknowledgement is tied to the redirect's CURRENT state
|
|
242
|
+
|
|
243
|
+
`acknowledgeDeletions` took bare GUIDs, which were guessable: a caller could
|
|
244
|
+
name one on the first call and never be shown what it destroys. Worse, an id
|
|
245
|
+
outlives the redirect behind it — Wix lets a create supply its own GUID, so
|
|
246
|
+
between a refusal and the retry the same id can point somewhere else, and the
|
|
247
|
+
acknowledgement would authorise deleting something the user never saw.
|
|
248
|
+
|
|
249
|
+
The refusal now hands back a `token` derived from the redirect's id, paths,
|
|
250
|
+
language and group flag, **and the effective site**, HMAC'd with a secret minted
|
|
251
|
+
once per process. It cannot be produced without having listed, it stops matching
|
|
252
|
+
the moment the redirect changes — in which case the call refuses again with the
|
|
253
|
+
new one — and a token earned on one site does not unlock its twin on another.
|
|
254
|
+
Two allowed sites can hold an identical redirect, since Wix accepts
|
|
255
|
+
caller-supplied GUIDs, and a cross-site replay would have authorised a deletion
|
|
256
|
+
on a tenant whose state nobody was shown.
|
|
257
|
+
|
|
258
|
+
### Changed — the two kinds of bulk loop are told apart, and only one is guarded
|
|
259
|
+
|
|
260
|
+
An intermediate version of this work refused any batch whose members looped on
|
|
261
|
+
each other, on the assumption that one would delete the other. The reference says
|
|
262
|
+
otherwise: an item closing a loop with an EARLIER item of the same request fails
|
|
263
|
+
on its own with `REDIRECT_LOOP` and the rest of the batch goes ahead — nothing
|
|
264
|
+
is deleted. That guard blocked migrations Wix would have applied in part, and
|
|
265
|
+
told the operator redirects would be lost when none would. It is gone.
|
|
266
|
+
|
|
267
|
+
What remains guarded is the deletion the reference does document: an item that
|
|
268
|
+
closes a loop with a redirect ALREADY ON THE SITE is created and that redirect is
|
|
269
|
+
deleted. The tool description now states both cases separately.
|
|
270
|
+
|
|
271
|
+
The declared bound also drops from 500 to **100** for bulk create, which is what
|
|
272
|
+
the method page and the Developer Preview index both say. Bulk delete stays 500.
|
|
273
|
+
|
|
274
|
+
### Fixed — irreversible SEO writes were being replayed
|
|
275
|
+
|
|
276
|
+
The HTTP client retries 429/502/503/504 twice. A create that Wix applied before
|
|
277
|
+
answering 503 — deleting a loop-closing redirect on the way — was then applied
|
|
278
|
+
again, and neither answer says which happened. `WixRequestOptions` gained
|
|
279
|
+
`retry`, and all four writes set it to `false` — a delete Wix applied before
|
|
280
|
+
answering 503 comes back 404 on the replay, and the plugin would then report a
|
|
281
|
+
failure for a permanent deletion that did happen: the error surfaces and the caller
|
|
282
|
+
re-reads, instead of a second irreversible write nobody asked for.
|
|
283
|
+
|
|
284
|
+
### Fixed — an unsupported field rode through to Wix
|
|
285
|
+
|
|
286
|
+
TypeBox objects accept properties a schema does not mention, so a per-item
|
|
287
|
+
`options.forceReplace` on a bulk create survived validation and reached Wix —
|
|
288
|
+
while the preflight had been run with `forceReplace: false` and the approval
|
|
289
|
+
prompt never showed the nested flag. Wix deletes the redirect holding that
|
|
290
|
+
`from` path. Both creates now send a redirect rebuilt from the fields the plugin
|
|
291
|
+
actually supports; `forceReplace` is honoured only as the single create's own
|
|
292
|
+
top-level parameter, where the preflight accounts for it. The stripping is in
|
|
293
|
+
the code rather than left to the schema, because `execute` can be called
|
|
294
|
+
directly.
|
|
295
|
+
|
|
296
|
+
### Changed — a delete names a redirect, not just an id
|
|
297
|
+
|
|
298
|
+
`wix_seo_delete_redirect` sent the DELETE straight from a GUID. Wix accepts
|
|
299
|
+
caller-supplied GUIDs, so between a listing, the operator's approval and the
|
|
300
|
+
call, the same id can name a different redirect — and the prompt showed nothing
|
|
301
|
+
but the id. The redirect is now re-read and the call refuses, showing its paths,
|
|
302
|
+
language and scope, until the `token` from that refusal comes back. Same protocol
|
|
303
|
+
as the creates, and the same staleness: if the redirect changed, the token no
|
|
304
|
+
longer matches. The bulk delete follows it too — it had been sending up to 500
|
|
305
|
+
GUIDs straight through, with an approval prompt carrying ids and no paths.
|
|
306
|
+
|
|
307
|
+
### Fixed — the SEO opt-in is reachable from the configurator
|
|
308
|
+
|
|
309
|
+
`enableSeoTools` defaults to false and had no `uiHints` entry, and OpenClaw's
|
|
310
|
+
configuration flow prompts only for the properties declared there. The switch
|
|
311
|
+
that registers the nine SEO tools was therefore unreachable without hand-editing
|
|
312
|
+
the config file — the feature shipped off, with no supported way to turn it on.
|
|
313
|
+
A manifest test now requires a `uiHints` entry for every configurable property.
|
|
314
|
+
|
|
315
|
+
### Fixed — a value cannot paint its own lines in the approval prompt
|
|
316
|
+
|
|
317
|
+
The description is rendered as a line-structured message, and the redirect
|
|
318
|
+
rendering — which exists to be more readable than JSON — put paths, languages
|
|
319
|
+
and parameter names into it verbatim. A `from` of `/a\nsite: attacker-site` drew
|
|
320
|
+
a second field naming a site the call never touched, on the one surface whose
|
|
321
|
+
purpose is a human deciding about an irreversible write. The generic branch had
|
|
322
|
+
been safe only because `JSON.stringify` escapes controls. Controls are now made
|
|
323
|
+
visible before any truncation, at every insertion point, and `language` is
|
|
324
|
+
bounded like the paths.
|
|
325
|
+
|
|
326
|
+
### Fixed — an `options` shape this version cannot read is refused
|
|
327
|
+
|
|
328
|
+
`options: []` is `typeof "object"` and passed the pre-flight, as did a drift
|
|
329
|
+
renaming the flag to `isGroupRedirect`. Either left a group redirect described
|
|
330
|
+
to the operator as exact and its acknowledgement token blind to the scope it
|
|
331
|
+
really has — the two things the token exists to bind. `options` must now be a
|
|
332
|
+
plain object carrying no key but `groupRedirect`.
|
|
333
|
+
|
|
334
|
+
### Fixed — the prompt names both ways a create destroys
|
|
335
|
+
|
|
336
|
+
A redirect takes precedence over a real page at the same path, so creating one
|
|
337
|
+
from a path that still serves a page makes that page unreachable — no existing
|
|
338
|
+
redirect involved, and nothing the pre-flight can see, since it reads redirects
|
|
339
|
+
rather than pages. Both create prompts warned only about deleting a redirect.
|
|
340
|
+
They now name the hidden page too, which is the operator's only warning about it.
|
|
341
|
+
|
|
342
|
+
The `Manage SEO` scope also moved out of the README's "minimum required" list.
|
|
343
|
+
In the default configuration `enableSeoTools` is false and the SEO tools are not
|
|
344
|
+
registered, so granting a scope that permits deleting redirects buys nothing —
|
|
345
|
+
it is listed as conditional on turning them on.
|
|
346
|
+
|
|
347
|
+
### Fixed — a create that points under another language's prefix
|
|
348
|
+
|
|
349
|
+
The language filter ran before any path comparison, for both checks. Two
|
|
350
|
+
languages are separate URL namespaces, so that is right for a create pointing
|
|
351
|
+
inside its own — refusing there would block a create that destroys nothing. But
|
|
352
|
+
a caller can write the other language's prefix explicitly: an `en` redirect
|
|
353
|
+
pointing at `/fr/blog` names the public entry of the `fr` redirect Wix stores as
|
|
354
|
+
`/blog`, and the filter dropped that pair before the comparison that would have
|
|
355
|
+
recognised it, so the deletion went unannounced. That case is now detected; none
|
|
356
|
+
is taken away. Two related questions upstream does not answer — whether a group
|
|
357
|
+
redirect resolves a loop for destinations merely UNDER its `from`, and how the
|
|
358
|
+
two languages compare in general — are recorded in the code as known bounds
|
|
359
|
+
rather than guessed at, because settling them needs a create and a delete
|
|
360
|
+
against a live site.
|
|
361
|
+
|
|
362
|
+
A global redirect — one with no `language` — applies to every language, so it is
|
|
363
|
+
also entered at `/de/blog` and not only at `/blog`. Against such a rule the scope
|
|
364
|
+
comparison always "overlaps", which is precisely what kept the foreign-prefix
|
|
365
|
+
check from running, and a create pointing at `/de/blog` closed a loop with it
|
|
366
|
+
unannounced. The site's configured languages cannot be read here (that endpoint
|
|
367
|
+
needs a different scope and can answer 403), so a leading segment SHAPED like a
|
|
368
|
+
language tag is now recognised. It errs towards announcing: a `/fr/...` that is
|
|
369
|
+
really a content folder costs one acknowledgement, and the operator sees exactly
|
|
370
|
+
which redirect is named.
|
|
371
|
+
|
|
372
|
+
### Fixed — the create describes the protocol it implements
|
|
373
|
+
|
|
374
|
+
Two sentences of `wix_seo_create_redirect`'s description sent a model down paths
|
|
375
|
+
the code refuses: it named Wix's own `options.forceReplace` — the nested flag
|
|
376
|
+
this plugin strips, so the create would have failed as if the flag were never
|
|
377
|
+
set — and it asked for the doomed redirects' GUIDs, where the acknowledgement
|
|
378
|
+
protocol takes the `token` from the refusal and rejects a bare id. The behaviour
|
|
379
|
+
was right throughout; only the contract a model reads before acting was wrong,
|
|
380
|
+
which no behavioural test could catch. Both are corrected, the bulk create says
|
|
381
|
+
`token` explicitly too, and a test now holds the descriptions to it.
|
|
382
|
+
|
|
383
|
+
### Fixed — the pre-flight trusts a listing only when it understands all of it
|
|
384
|
+
|
|
385
|
+
The destructive pre-flight refused a redirect listing whose entries had no
|
|
386
|
+
string `from`, but the acknowledgement token also binds `id`, `to`, `language`
|
|
387
|
+
and `groupRedirect`, substituting a default for whatever is absent. A drift that
|
|
388
|
+
renamed or dropped `to` — a stated possibility on a Developer Preview API —
|
|
389
|
+
therefore degraded the token into one that no longer changed when the redirect's
|
|
390
|
+
destination did, and a stale acknowledgement would have authorised a permanent
|
|
391
|
+
deletion. Every field the token binds is now checked, scope fields for shape.
|
|
392
|
+
|
|
393
|
+
### Fixed — the write lock forgets, and the audit pages as the endpoint defines
|
|
394
|
+
|
|
395
|
+
`serializedBySite` kept one settled promise per site id it had ever been given.
|
|
396
|
+
The key is caller-supplied and queued BEFORE the whitelist rejects it, so a
|
|
397
|
+
long-lived gateway retained an entry for every string ever passed — the comment
|
|
398
|
+
already claimed the chain was dropped "once it is the last one"; now the code
|
|
399
|
+
does it, and only while it is still the tail.
|
|
400
|
+
|
|
401
|
+
`wix_seo_list_item_tags` advertised the shared GET paging envelope, `offset`
|
|
402
|
+
included, on an endpoint that pages by cursor alone. The schema is now
|
|
403
|
+
cursor-only AND the query is built from those two keys: narrowing a TypeBox
|
|
404
|
+
object does not strip what it omits, and `execute` can be called directly, so
|
|
405
|
+
an `offset` arriving at runtime would still have been forwarded.
|
|
406
|
+
|
|
407
|
+
The smoke test no longer probes the SEO endpoints unless `WIX_SMOKE_SEO=1`.
|
|
408
|
+
They would otherwise have demanded `SCOPE.PROMOTE.MANAGE-SEO` — a scope that
|
|
409
|
+
also permits deleting redirects — of every key, including the default
|
|
410
|
+
configuration where the SEO tools are not even registered.
|
|
411
|
+
|
|
412
|
+
### Fixed — a refusal names the whole loss, and the protocol converges
|
|
413
|
+
|
|
414
|
+
Every refusal listed only the redirects still lacking an acknowledgement, while
|
|
415
|
+
`howToProceed` asked for "every token above". The phrase therefore named a
|
|
416
|
+
different set on each round: a caller that REPLACED its token list rather than
|
|
417
|
+
extending it alternated between halves and never got through, so a legitimate
|
|
418
|
+
multi-redirect deletion was impossible. The shrinking list also understated the
|
|
419
|
+
loss — the second prompt showed fewer deletions than the first. All three
|
|
420
|
+
multi-target refusals (single create, bulk create, bulk delete) now list the
|
|
421
|
+
complete set, each entry carrying `acknowledged: true | false`.
|
|
422
|
+
|
|
423
|
+
### Fixed — an omitted parameter is always marked as omitted
|
|
424
|
+
|
|
425
|
+
The approval description reserved room for the ` · …` marker only until the
|
|
426
|
+
first part was dropped, and stopped reserving exactly when the marker had become
|
|
427
|
+
certain. Parts after that drop filled the 256-character budget to the brim and
|
|
428
|
+
the final truncation cut the marker off, so a prompt that had silently dropped a
|
|
429
|
+
parameter read as complete — on the destructive calls where it matters. Room is
|
|
430
|
+
now reserved from the first drop onwards, and trailing parts are surrendered if
|
|
431
|
+
that is what it takes for the marker to survive.
|
|
432
|
+
|
|
433
|
+
The approval fingerprint moved from a 32-bit FNV to SHA-256 truncated to 128
|
|
434
|
+
bits, over a key-sorted serialisation. The old one was small enough to collide
|
|
435
|
+
deliberately — two batches whose sampled members matched then produced identical
|
|
436
|
+
prompts for two different permanent deletions — and insertion-order
|
|
437
|
+
serialisation gave the same batch different identities depending on how its
|
|
438
|
+
fields happened to be written.
|
|
439
|
+
|
|
440
|
+
### Fixed — SEO writes ignored cancellation
|
|
441
|
+
|
|
442
|
+
The tool factory hands each run an `AbortSignal` and the HTTP client honours it,
|
|
443
|
+
but the SEO tools never took it. A destructive write can sit behind another
|
|
444
|
+
site-write for as long as that one takes, so an irreversible POST could go out
|
|
445
|
+
after the runtime had already reported the operation cancelled. The signal is
|
|
446
|
+
now checked after the queue and again immediately before the write, and passed
|
|
447
|
+
to every request — the five READ tools had never declared it either, so a
|
|
448
|
+
cancelled audit went on waiting, retrying and spending quota. The retry backoff
|
|
449
|
+
is interruptible too: a plain timer meant an abandoned call still sat out the
|
|
450
|
+
whole wait, which `Retry-After` can make long, before noticing.
|
|
451
|
+
|
|
452
|
+
### Fixed — the manifest tests were checking a list they maintained themselves
|
|
453
|
+
|
|
454
|
+
`test/manifest.test.ts` re-listed the tool domains instead of reading what the
|
|
455
|
+
plugin registers, so adding a whole domain left its guards passing over the new
|
|
456
|
+
tools without seeing them. Registration now goes through a single exported
|
|
457
|
+
`buildAllTools()` that the tests read, and two guards were added: `contracts.tools`
|
|
458
|
+
must name exactly the registered tools, and the manifest's `approvalRequired`
|
|
459
|
+
default must match the code's.
|
|
460
|
+
|
|
461
|
+
### Fixed — the build no longer depends on a package nobody declared
|
|
462
|
+
|
|
463
|
+
`src/tools/_factory.ts` imported the `AgentToolResult` type from
|
|
464
|
+
`@mariozechner/pi-agent-core`, which was never a dependency of this plugin: it
|
|
465
|
+
arrived only because `openclaw` happened to pull it in. Upstream dropped it
|
|
466
|
+
(absent from 2026.6.x onwards), so the import resolved on a machine holding an
|
|
467
|
+
older tree and nowhere else — a fresh install compiled no more.
|
|
468
|
+
|
|
469
|
+
The shape is now declared in the plugin itself. `openclaw/plugin-sdk/agent-core`
|
|
470
|
+
re-exports the same type, but that subpath only exists from 2026.5.28 while this
|
|
471
|
+
plugin supports `>=2026.5.0`: importing it would have quietly dropped the bottom
|
|
472
|
+
of the declared range. The local declaration holds across the whole of it, and
|
|
473
|
+
the structural check still happens where it matters — at `registerTool`, against
|
|
474
|
+
whichever SDK is installed. Verified compiling at both ends of the range, 2026.5.2
|
|
475
|
+
and 2026.6.34.
|
|
476
|
+
|
|
477
|
+
Two related repairs came with it. The default export is now annotated
|
|
478
|
+
(`ReturnType<typeof definePluginEntry>`): its inferred type reached into an
|
|
479
|
+
openclaw build-internal chunk that TypeScript cannot name from a declaration
|
|
480
|
+
file (`TS2742`). And `package-lock.json`, stale at `0.1.1` since that release,
|
|
481
|
+
is regenerated — `npm ci` had been failing on every run since, silently falling
|
|
482
|
+
back to `npm install`, which is why a dependency that had quietly vanished
|
|
483
|
+
upstream went unnoticed until now.
|
|
484
|
+
|
|
485
|
+
### Migration
|
|
486
|
+
|
|
487
|
+
For instance owners on `@lacneu/wix-openclaw@0.2.2`:
|
|
488
|
+
|
|
489
|
+
1. `openclaw plugins update @lacneu/wix-openclaw` — **`update`, not
|
|
490
|
+
`install`**: `install` rewrites `plugins.entries.wix-openclaw` with the
|
|
491
|
+
manifest's defaults, discarding the API key, the site whitelist and any
|
|
492
|
+
`approvalRequired` you have tuned. `update` preserves them.
|
|
493
|
+
2. Restart the gateway container. This release adds no environment variable, so
|
|
494
|
+
a `restart` is enough — no `--force-recreate` needed.
|
|
495
|
+
3. Verify in the boot log:
|
|
496
|
+
`wix-openclaw: ready — 50 tool(s) registered`.
|
|
497
|
+
|
|
498
|
+
Fifty, not sixty-nine: the SEO tools are not registered until you opt in. Nothing
|
|
499
|
+
else changes, and the approval fix applies as soon as the plugin loads — a gated
|
|
500
|
+
tool that used to fail without asking will now put its prompt to the operator.
|
|
501
|
+
|
|
502
|
+
To add the SEO tools:
|
|
503
|
+
|
|
504
|
+
1. Tick **Manage SEO** (`SCOPE.PROMOTE.MANAGE-SEO`) on the Wix API key used by
|
|
505
|
+
that instance. Its label
|
|
506
|
+
is untranslated in the Wix UI — it sits under *All site permissions*, between
|
|
507
|
+
*Social Posts* and *Import orders*. There is no read-only variant: the same
|
|
508
|
+
permission that allows the audit also allows deleting redirects.
|
|
509
|
+
2. Set `enableSeoTools: true` — via the configurator, which prompts for it as
|
|
510
|
+
*Enable SEO tools (audit + URL redirects)*, or with
|
|
511
|
+
`openclaw config set plugins.entries.wix-openclaw.config.enableSeoTools true`.
|
|
512
|
+
`update` carries your existing config forward and the new field simply
|
|
513
|
+
defaults to `false`, so nothing turns on by surprise.
|
|
514
|
+
3. Restart, and expect `69 tool(s) registered`.
|
|
515
|
+
4. Read the Developer Preview warning at the top of this section before pointing
|
|
516
|
+
any of the four writing tools at a live site.
|
|
517
|
+
|
|
10
518
|
## [0.2.2] - 2026-05-03
|
|
11
519
|
|
|
12
520
|
### Fixed — OpenClaw 2026.5.x compatibility
|
|
@@ -236,5 +744,6 @@ workaround; await the upstream release.
|
|
|
236
744
|
- Release workflow using **npm Trusted Publishing (OIDC)** — no `NPM_TOKEN`
|
|
237
745
|
secret required, provenance attached automatically.
|
|
238
746
|
|
|
239
|
-
[Unreleased]: https://github.com/OlivierNeu/wix-openclaw-plugin/compare/v0.
|
|
747
|
+
[Unreleased]: https://github.com/OlivierNeu/wix-openclaw-plugin/compare/v0.3.0...HEAD
|
|
748
|
+
[0.3.0]: https://github.com/OlivierNeu/wix-openclaw-plugin/compare/v0.2.2...v0.3.0
|
|
240
749
|
[0.1.0]: https://github.com/OlivierNeu/wix-openclaw-plugin/releases/tag/v0.1.0
|