@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/README.md
CHANGED
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
## Overview
|
|
16
16
|
|
|
17
17
|
`wix-openclaw` is an OpenClaw plugin that lets your agent operate a Wix site
|
|
18
|
-
through the official Wix REST API. It registers ~
|
|
18
|
+
through the official Wix REST API. It registers ~60 tools split across a dozen
|
|
19
19
|
Wix products (Blog, CMS Data, Forms, Bookings, Contacts, Events, Reviews, FAQ,
|
|
20
|
-
Multilingual, Sites, Site Media), plus a `wix_design_brief` helper that
|
|
21
|
-
loose textual briefs into structured markdown artefacts.
|
|
20
|
+
Multilingual, Sites, Site Media, SEO), plus a `wix_design_brief` helper that
|
|
21
|
+
turns loose textual briefs into structured markdown artefacts.
|
|
22
22
|
|
|
23
23
|
Every tool funnels through a single authenticated HTTP client that:
|
|
24
24
|
|
|
@@ -175,6 +175,21 @@ should stay unticked.
|
|
|
175
175
|
Tick these only if the corresponding Wix product is actually in use on
|
|
176
176
|
the target site:
|
|
177
177
|
|
|
178
|
+
- [ ] **Manage SEO** (`SCOPE.PROMOTE.MANAGE-SEO`) — **only if you set
|
|
179
|
+
`enableSeoTools: true`**, which registers **nineteen** tools of which
|
|
180
|
+
**eleven write irreversibly** (four on redirects, seven on tags and
|
|
181
|
+
patterns; a pattern write re-renders every page of its type). In the default
|
|
182
|
+
configuration the SEO tools are not registered at all, so granting this scope would hand the key the power to
|
|
183
|
+
delete redirects for tools that cannot run. Wix exposes a **single** SEO
|
|
184
|
+
scope, not a read/write pair: ticking it grants redirect creation and deletion
|
|
185
|
+
along with the read-only audit, so there is no such thing as an audit-only SEO
|
|
186
|
+
key. In the Wix UI the checkbox sits under **All site permissions**, between
|
|
187
|
+
*Social Posts* and *Import orders*; its label is currently untranslated and
|
|
188
|
+
renders literally as `site.scope.SCOPE.PROMOTE.MANAGE-SEO.name`. Since Wix
|
|
189
|
+
cannot narrow it, the read/write boundary is enforced by this plugin instead —
|
|
190
|
+
each of the eleven writing tools refuses until what it would destroy, be it a
|
|
191
|
+
redirect, a tag or a template, has been named and acknowledged.
|
|
192
|
+
|
|
178
193
|
- [ ] **Pricing Plans** — if you sell subscription packages
|
|
179
194
|
- [ ] **Wix Stores** + **Wix eCommerce** — if you sell physical/digital
|
|
180
195
|
products. Without these, the `wix_*` tools that touch e-commerce
|
|
@@ -225,6 +240,7 @@ columns and remove the matching tools from your agent's allow-list.
|
|
|
225
240
|
| `wix_reviews_*` | Wix Reviews | Reviews moderation |
|
|
226
241
|
| `wix_faq_*` | Manage FAQ | FAQ tooling |
|
|
227
242
|
| `wix_multilingual_*` | Multilingual Translation Schema Read | Translation reads |
|
|
243
|
+
| `wix_seo_*` (all nineteen) | Manage SEO (`SCOPE.PROMOTE.MANAGE-SEO`) | SEO audit **and** redirect changes — Wix has no read-only SEO scope, so this one permission covers both |
|
|
228
244
|
| `wix_design_brief` | _(none — no API call)_ | Always |
|
|
229
245
|
|
|
230
246
|
### Wix app installation requirements
|
|
@@ -237,7 +253,7 @@ working as soon as the app is installed (no plugin upgrade needed).
|
|
|
237
253
|
| Tool group | Wix app required | Error returned when missing |
|
|
238
254
|
|---|---|---|
|
|
239
255
|
| `wix_blog_*` | Wix Blog | `404` |
|
|
240
|
-
| `wix_data_*` | Velo / Wix Code
|
|
256
|
+
| `wix_data_*` | Wix CMS (Velo / Wix Code — enable in Editor) | `400 WDE0110: Wix CMS app is not installed for site` |
|
|
241
257
|
| `wix_forms_*` | Wix Forms | `404` |
|
|
242
258
|
| `wix_bookings_*` | Wix Bookings | `404` |
|
|
243
259
|
| `wix_events_*` | Wix Events | `428 WIX_EVENTS_APP_NOT_INSTALLED` |
|
|
@@ -338,7 +354,9 @@ site; `app-not-installed` and `velo-not-enabled` mean the tool's
|
|
|
338
354
|
endpoint exists but its backing Wix product is not active on the
|
|
339
355
|
smoke-test site (the request still reaches Wix and the path/headers
|
|
340
356
|
are correct); `docs` means write/destructive endpoints that we do not
|
|
341
|
-
exercise live.
|
|
357
|
+
exercise live. The SEO reads are skipped by the smoke test unless
|
|
358
|
+
`WIX_SMOKE_SEO=1` is set, so a key without `SCOPE.PROMOTE.MANAGE-SEO` is not
|
|
359
|
+
failed for lacking a scope the default configuration never uses.
|
|
342
360
|
|
|
343
361
|
### Blog
|
|
344
362
|
|
|
@@ -455,15 +473,98 @@ Requires the Wix FAQ app (returns `400 App is not installed` otherwise).
|
|
|
455
473
|
|
|
456
474
|
### Multilingual (read-only in V1)
|
|
457
475
|
|
|
458
|
-
Returns `403` on the Ataraxis smoke-test site
|
|
459
|
-
`Multilingual Translation Schema Read` permission
|
|
460
|
-
|
|
476
|
+
Returns `403` on the Ataraxis smoke-test site, with an EMPTY body. The earlier
|
|
477
|
+
guess — a missing `Multilingual Translation Schema Read` permission — has since
|
|
478
|
+
been ruled out: that permission is granted on the key and the `403` persists.
|
|
479
|
+
What remains undetermined is whether the Wix Multilingual app is simply not
|
|
480
|
+
installed, or whether these two endpoints also require the separate
|
|
481
|
+
`SCOPE.MULTILINGUAL.MANAGE_TRANSLATIONS` scope. The empty body does not say, and
|
|
482
|
+
neither answer has been tested.
|
|
461
483
|
|
|
462
484
|
| Tool | What it does | Approval | Validated |
|
|
463
485
|
|---|---|---|---|
|
|
464
486
|
| `wix_multilingual_list_languages` | List configured languages; standard query envelope | — | 403-permission |
|
|
465
487
|
| `wix_multilingual_list_schemas` | List translation schemas | — | 403-permission |
|
|
466
488
|
|
|
489
|
+
### SEO
|
|
490
|
+
|
|
491
|
+
> **Developer Preview, and OFF by default.** Every Wix endpoint behind these
|
|
492
|
+
> nineteen tools is listed in Wix's
|
|
493
|
+
> [Developer Preview](https://dev.wix.com/docs/api-reference/articles/work-with-wix-apis/platform/about-developer-preview)
|
|
494
|
+
> — Item SEO Tags, Site SEO Tags, SEO Patterns and all six Redirects methods.
|
|
495
|
+
> Wix may change
|
|
496
|
+
> them at any time, and states plainly that you **shouldn't use them on live
|
|
497
|
+
> sites**: they are for testing and feedback. An API stays in preview for at
|
|
498
|
+
> most six months.
|
|
499
|
+
>
|
|
500
|
+
> Nothing here registers unless you set `enableSeoTools: true` in the plugin
|
|
501
|
+
> config. Eleven of the tools write irreversibly to the live site the moment
|
|
502
|
+
> they succeed — and a pattern write re-renders every page of its type — so
|
|
503
|
+
> opting in is the operator saying they accept both risks.
|
|
504
|
+
> _Preview status verified against the Wix reference on 2026-08-28._
|
|
505
|
+
|
|
506
|
+
Auditing a site's SEO, correcting its tags, and managing its URL redirects and
|
|
507
|
+
tag patterns.
|
|
508
|
+
|
|
509
|
+
| Tool | What it does | Approval | Validated |
|
|
510
|
+
|---|---|---|---|
|
|
511
|
+
| `wix_seo_list_item_tags` | List the items of one type with their SEO tags. `STATIC_PAGE` enumerates the site's pages. Reports `hasOverride`, the item's own `tags`, and `resolvedTags` marked with each tag's source. Cursor paging. | — | live |
|
|
512
|
+
| `wix_seo_get_item_tags` | One item's tags, own and resolved | — | schema |
|
|
513
|
+
| `wix_seo_get_site_tags` | Site-wide tags, plus dashboard verification embeds (read-only) | — | live |
|
|
514
|
+
| `wix_seo_list_redirects` | Every redirect on the site. No filter, sort or paging — the endpoint takes none | — | live |
|
|
515
|
+
| `wix_seo_get_redirect` | One redirect by GUID | — | schema |
|
|
516
|
+
| `wix_seo_create_redirect` | Create a 301 redirect | **required** | schema |
|
|
517
|
+
| `wix_seo_delete_redirect` | Delete one redirect (re-reads it and refuses until its token is acknowledged) | **required** | schema |
|
|
518
|
+
| `wix_seo_bulk_create_redirects` | Create 1–100 redirects | **required** | schema |
|
|
519
|
+
| `wix_seo_bulk_delete_redirects` | Delete 1–500 redirects | **required** | schema |
|
|
520
|
+
| `wix_seo_set_item_tags` | Set one item's tags — REPLACES its own set in full; refuses until the tags it would drop are acknowledged | **required** | schema |
|
|
521
|
+
| `wix_seo_bulk_set_item_tags` | Set tags for many items of one type (100 max, 20 for `BLOG_POST`); per-entry results | **required** | schema |
|
|
522
|
+
| `wix_seo_reset_item_tags` | Give an item back the tags it inherits, discarding its own (all or nothing) | **required** | schema |
|
|
523
|
+
| `wix_seo_set_site_tags` | Set the site-wide tags — `meta` only; Wix rejects `title`/`script`/`link` here | **required** | schema |
|
|
524
|
+
| `wix_seo_list_patterns` | The site's tag templates, per page type | — | schema |
|
|
525
|
+
| `wix_seo_get_pattern` | One page type's pattern; read `source` to know whose it is — an owned pattern may hold zero templates, so empty ≠ absent | — | schema |
|
|
526
|
+
| `wix_seo_list_pattern_variables` | The variables a page type's pattern may reference | — | schema |
|
|
527
|
+
| `wix_seo_set_pattern` | Change an existing pattern — re-renders EVERY page of the type | **required** | schema |
|
|
528
|
+
| `wix_seo_create_pattern` | Give a page type its first pattern (same path as the set, `POST` not `PATCH`) | **required** | schema |
|
|
529
|
+
| `wix_seo_reset_pattern` | Return a page type to the Wix default pattern | **required** | schema |
|
|
530
|
+
|
|
531
|
+
**Three things to know before using these.**
|
|
532
|
+
|
|
533
|
+
*A read of a static page is the SAVED revision, not the live one.* For
|
|
534
|
+
`STATIC_PAGE`, `tags`, `focusKeywords` and `resolvedTags` always reflect the
|
|
535
|
+
saved revision, and `publishStatus` is always `PUBLISH_STATUS_UNSPECIFIED` — the
|
|
536
|
+
API has no path to read the published revision back. An audit report should say
|
|
537
|
+
it is about the saved page. `resolvedTags` also omits anything site code or apps
|
|
538
|
+
add while the page renders, so it is not a copy of the rendered `<head>`.
|
|
539
|
+
|
|
540
|
+
*Creating a redirect can delete another one.* Redirects do not chain: if an
|
|
541
|
+
existing redirect **starts at** the path a new one points to, that existing
|
|
542
|
+
redirect is deleted and the create proceeds — no flag involved, no way back.
|
|
543
|
+
The tool's own top-level `forceReplace` parameter additionally deletes a
|
|
544
|
+
redirect holding the same `from` path. Wix documents that flag nested as
|
|
545
|
+
`options.forceReplace`; do **not** send it there — the tool rebuilds the
|
|
546
|
+
redirect from the fields it supports, so a nested flag is dropped and the create
|
|
547
|
+
fails with `FROM_URL_EXISTS` as if it had never been set. This is why
|
|
548
|
+
`wix_seo_create_redirect` is approval-gated by default
|
|
549
|
+
alongside the deletes. A redirect also takes precedence over a real page at the
|
|
550
|
+
same path, which makes that page unreachable.
|
|
551
|
+
|
|
552
|
+
*The create refuses before it destroys.* `wix_seo_create_redirect` and
|
|
553
|
+
`wix_seo_bulk_create_redirects` list the site's redirects themselves and refuse,
|
|
554
|
+
naming each redirect they would delete — with its paths, language and whether it
|
|
555
|
+
is a group rule — until the caller repeats the `token` from that refusal back in
|
|
556
|
+
`acknowledgeDeletions`. The token is derived from the redirect's current state,
|
|
557
|
+
so it cannot be guessed without listing and goes stale if the redirect changes.
|
|
558
|
+
The check is in the execution path, not in the tool description, so a direct call
|
|
559
|
+
gets it too, and it refuses outright rather than assuming an empty list when the
|
|
560
|
+
redirects cannot be read. Destructive writes are serialised per site; a change
|
|
561
|
+
made in the Wix dashboard between the read and the write remains possible.
|
|
562
|
+
|
|
563
|
+
*There is no update method and no query method.* To change a redirect: get it,
|
|
564
|
+
delete it, then create it carrying **every** field over — an omitted field is
|
|
565
|
+
filled with its default, not its previous value. To find one, list them all and
|
|
566
|
+
filter client-side.
|
|
567
|
+
|
|
467
568
|
### Design
|
|
468
569
|
|
|
469
570
|
| Tool | What it does | Approval | Validated |
|
package/dist/config.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { ResolvedWixConfig, WixPluginConfig } from "./types.js";
|
|
|
6
6
|
* become empty strings to avoid leaking `undefined` into downstream code.
|
|
7
7
|
*/
|
|
8
8
|
export declare function resolveEnv<T>(value: T): T;
|
|
9
|
+
export declare const DEFAULT_APPROVAL_REQUIRED: string[];
|
|
9
10
|
/**
|
|
10
11
|
* Apply defaults and env substitution to the raw plugin config.
|
|
11
12
|
*
|
package/dist/config.js
CHANGED
|
@@ -15,7 +15,7 @@ export function resolveEnv(value) {
|
|
|
15
15
|
return process.env[name] ?? "";
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
|
-
const DEFAULT_APPROVAL_REQUIRED = [
|
|
18
|
+
export const DEFAULT_APPROVAL_REQUIRED = [
|
|
19
19
|
"wix_blog_publish_draft",
|
|
20
20
|
"wix_blog_unpublish",
|
|
21
21
|
"wix_blog_delete_draft",
|
|
@@ -25,6 +25,24 @@ const DEFAULT_APPROVAL_REQUIRED = [
|
|
|
25
25
|
"wix_bookings_cancel",
|
|
26
26
|
"wix_reviews_moderate",
|
|
27
27
|
"wix_faq_delete_question",
|
|
28
|
+
// SEO redirects. `create` is gated like the deletes on purpose: it is
|
|
29
|
+
// destructive by construction. Redirects do not chain — an existing redirect
|
|
30
|
+
// that STARTS AT the path a new one points to is deleted so the create can
|
|
31
|
+
// proceed, with no flag asked for and no way back. Classifying these by verb
|
|
32
|
+
// would have left the most damaging call in the set ungated.
|
|
33
|
+
"wix_seo_create_redirect",
|
|
34
|
+
"wix_seo_delete_redirect",
|
|
35
|
+
"wix_seo_bulk_create_redirects",
|
|
36
|
+
"wix_seo_bulk_delete_redirects",
|
|
37
|
+
// Tranche 2 — writing tags. Each REPLACES what it writes: the previous
|
|
38
|
+
// value is gone, and a pattern write re-renders every page of its type.
|
|
39
|
+
"wix_seo_set_item_tags",
|
|
40
|
+
"wix_seo_bulk_set_item_tags",
|
|
41
|
+
"wix_seo_reset_item_tags",
|
|
42
|
+
"wix_seo_set_site_tags",
|
|
43
|
+
"wix_seo_set_pattern",
|
|
44
|
+
"wix_seo_create_pattern",
|
|
45
|
+
"wix_seo_reset_pattern",
|
|
28
46
|
];
|
|
29
47
|
const VALID_LOG_LEVELS = ["debug", "info", "warn", "error"];
|
|
30
48
|
/**
|
|
@@ -49,6 +67,12 @@ export function resolveConfig(cfg = {}) {
|
|
|
49
67
|
: "info";
|
|
50
68
|
return {
|
|
51
69
|
enabled: cfg.enabled !== false,
|
|
70
|
+
// OPT-IN, unlike every other tool group. The Wix SEO endpoints are in
|
|
71
|
+
// Developer Preview: Wix may change them at any time and says they
|
|
72
|
+
// "shouldn't be used on live sites". Four of these tools also write
|
|
73
|
+
// irreversibly to a live site the moment they succeed. Defaulting them on
|
|
74
|
+
// would enable that on every existing install without anyone deciding to.
|
|
75
|
+
enableSeoTools: cfg.enableSeoTools === true,
|
|
52
76
|
apiKey,
|
|
53
77
|
accountId,
|
|
54
78
|
allowedSiteIds,
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,EAAE;AACF,2EAA2E;AAC3E,6DAA6D;AAQ7D;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAI,KAAQ;IACpC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,OAAO,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,EAAE;QACvD,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACjC,CAAC,CAAiB,CAAC;AACrB,CAAC;AAED,MAAM,yBAAyB,GAAG;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,EAAE;AACF,2EAA2E;AAC3E,6DAA6D;AAQ7D;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAI,KAAQ;IACpC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,OAAO,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,EAAE;QACvD,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACjC,CAAC,CAAiB,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,wBAAwB;IACxB,oBAAoB;IACpB,uBAAuB;IACvB,sBAAsB;IACtB,qBAAqB;IACrB,yBAAyB;IACzB,qBAAqB;IACrB,sBAAsB;IACtB,yBAAyB;IACzB,sEAAsE;IACtE,6EAA6E;IAC7E,2EAA2E;IAC3E,6EAA6E;IAC7E,6DAA6D;IAC7D,yBAAyB;IACzB,yBAAyB;IACzB,+BAA+B;IAC/B,+BAA+B;IAC/B,uEAAuE;IACvE,wEAAwE;IACxE,uBAAuB;IACvB,4BAA4B;IAC5B,yBAAyB;IACzB,uBAAuB;IACvB,qBAAqB;IACrB,wBAAwB;IACxB,uBAAuB;CACxB,CAAC;AAEF,MAAM,gBAAgB,GAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AAE3E;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAuB,EAAE;IAEzB,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IAClD,MAAM,cAAc,GAAG,CAAC,GAAG,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9E,MAAM,eAAe,GAAG,UAAU,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;IAC5D,MAAM,aAAa,GACjB,eAAe,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAE/C,MAAM,gBAAgB,GAAG,GAAG,CAAC,gBAAgB,IAAI,yBAAyB,CAAC;IAE3E,MAAM,QAAQ,GAAgB,gBAAgB,CAAC,QAAQ,CACrD,GAAG,CAAC,QAAuB,CAC5B;QACC,CAAC,CAAE,GAAG,CAAC,QAAwB;QAC/B,CAAC,CAAC,MAAM,CAAC;IAEX,OAAO;QACL,OAAO,EAAE,GAAG,CAAC,OAAO,KAAK,KAAK;QAC9B,sEAAsE;QACtE,mEAAmE;QACnE,oEAAoE;QACpE,0EAA0E;QAC1E,0EAA0E;QAC1E,cAAc,EAAE,GAAG,CAAC,cAAc,KAAK,IAAI;QAC3C,MAAM;QACN,SAAS;QACT,cAAc;QACd,aAAa;QACb,gBAAgB,EAAE,IAAI,GAAG,CAAC,gBAAgB,CAAC;QAC3C,QAAQ;KACT,CAAC;AACJ,CAAC"}
|
package/dist/hooks/approval.d.ts
CHANGED
|
@@ -21,6 +21,16 @@ export interface BeforeToolCallResult {
|
|
|
21
21
|
timeoutBehavior?: "allow" | "deny";
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* The approval description, guaranteed to satisfy the protocol.
|
|
26
|
+
*
|
|
27
|
+
* Ordered by what decides a refusal: the irreversible effect, the site the call
|
|
28
|
+
* actually lands on (the preview used to strip `siteId`, so on a multi-site key
|
|
29
|
+
* the operator never saw which tenant), then each highlighted parameter.
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildApprovalDescription(toolName: string, params: Record<string, unknown>, defaultSiteId: string): string;
|
|
32
|
+
/** The approval title, likewise bounded (protocol: 1–80). */
|
|
33
|
+
export declare function buildApprovalTitle(toolName: string): string;
|
|
24
34
|
/**
|
|
25
35
|
* Build a `before_tool_call` handler bound to a resolved Wix config.
|
|
26
36
|
* Extracted as a factory so tests can exercise it without a full plugin
|
package/dist/hooks/approval.js
CHANGED
|
@@ -11,25 +11,313 @@
|
|
|
11
11
|
// - `timeoutBehavior` accepts "allow" | "deny".
|
|
12
12
|
// - `pluginId` is set automatically by the hook runner — do not set it
|
|
13
13
|
// yourself.
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
14
15
|
const DEFAULT_TIMEOUT_MS = 600_000; // 10 minutes
|
|
15
|
-
const PARAM_PREVIEW_CHARS = 600;
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* THE PROTOCOL'S OWN LIMITS, not ours.
|
|
18
|
+
*
|
|
19
|
+
* `PluginApprovalRequestParamsSchema` (openclaw gateway, plugin-approvals)
|
|
20
|
+
* declares `title` as 1–80 characters and `description` as 1–256. A longer one
|
|
21
|
+
* is not truncated by the gateway — the request fails validation, so the
|
|
22
|
+
* operator is never asked and the tool is blocked. The previous prompt embedded
|
|
23
|
+
* a 600-character JSON preview under a paragraph of boilerplate and could not
|
|
24
|
+
* fit: every gated tool was affected, not only the SEO ones.
|
|
25
|
+
*
|
|
26
|
+
* So the description is BUILT to the budget rather than written and hoped for.
|
|
20
27
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
const APPROVAL_TITLE_MAX = 80;
|
|
29
|
+
const APPROVAL_DESCRIPTION_MAX = 256;
|
|
30
|
+
/**
|
|
31
|
+
* Parameters that must reach the operator, in priority order.
|
|
32
|
+
*
|
|
33
|
+
* With 256 characters there is no room for the full parameters, so what is
|
|
34
|
+
* shown has to be what DECIDES: a redirect carries two free-form paths, and
|
|
35
|
+
* `forceReplace` — the flag that deletes an existing redirect for good — must
|
|
36
|
+
* never be the part that falls off the end.
|
|
37
|
+
*
|
|
38
|
+
* TABLE-DRIVEN so a destructive parameter cannot be added to a tool without
|
|
39
|
+
* saying where it ranks in the prompt.
|
|
40
|
+
*/
|
|
41
|
+
const APPROVAL_HIGHLIGHTS = {
|
|
42
|
+
wix_seo_create_redirect: ["forceReplace", "redirect"],
|
|
43
|
+
wix_seo_delete_redirect: ["redirectId"],
|
|
44
|
+
wix_seo_bulk_create_redirects: ["redirects"],
|
|
45
|
+
wix_seo_bulk_delete_redirects: ["redirectIds"],
|
|
46
|
+
};
|
|
47
|
+
/** One-line effect warnings, shown FIRST because they are the reason to refuse. */
|
|
48
|
+
const APPROVAL_EFFECTS = {
|
|
49
|
+
// A create destroys in TWO ways, and the second needs no existing redirect:
|
|
50
|
+
// a redirect takes precedence over a real page at the same path, so creating
|
|
51
|
+
// one from a path that still serves a page makes that page unreachable. The
|
|
52
|
+
// pre-flight cannot see it — it reads redirects, not pages — so the prompt is
|
|
53
|
+
// the only place the operator can be told.
|
|
54
|
+
wix_seo_create_redirect: "may DELETE a redirect; HIDES any real page at `from` (permanent)",
|
|
55
|
+
wix_seo_delete_redirect: "permanent",
|
|
56
|
+
wix_seo_bulk_create_redirects: "not atomic; may DELETE redirects; HIDES real pages at a `from`",
|
|
57
|
+
wix_seo_bulk_delete_redirects: "permanent",
|
|
58
|
+
};
|
|
59
|
+
/** Longest a single value may occupy in the prompt, so one long path cannot
|
|
60
|
+
* crowd out everything after it. */
|
|
61
|
+
const VALUE_MAX = 60;
|
|
62
|
+
/** Make control characters VISIBLE instead of letting them act.
|
|
63
|
+
*
|
|
64
|
+
* The description is rendered as a line-structured message, so a `from` path,
|
|
65
|
+
* a language or a parameter name carrying a newline can paint fields that were
|
|
66
|
+
* never sent — on the one surface whose whole purpose is an operator deciding
|
|
67
|
+
* whether to allow an irreversible write. The generic branch was safe by
|
|
68
|
+
* accident, `JSON.stringify` escaping these; the redirect rendering, which
|
|
69
|
+
* exists precisely to be more readable than JSON, was not. */
|
|
70
|
+
function sanitize(value) {
|
|
71
|
+
return value.replace(
|
|
72
|
+
// eslint-disable-next-line no-control-regex
|
|
73
|
+
/[\u0000-\u001f\u007f\u0085\u2028\u2029]/g, (c) => {
|
|
74
|
+
if (c === "\n")
|
|
75
|
+
return "\\n";
|
|
76
|
+
if (c === "\r")
|
|
77
|
+
return "\\r";
|
|
78
|
+
if (c === "\t")
|
|
79
|
+
return "\\t";
|
|
80
|
+
return `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}`;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/** Shorten in the MIDDLE: a path's beginning and its end are what identify it,
|
|
84
|
+
* and two long paths sharing a prefix are told apart only by their tails.
|
|
85
|
+
* Neutralises controls FIRST, so truncation can never be what lets one
|
|
86
|
+
* through. */
|
|
87
|
+
function shorten(raw, max = VALUE_MAX) {
|
|
88
|
+
const value = sanitize(raw);
|
|
89
|
+
if (value.length <= max)
|
|
90
|
+
return value;
|
|
91
|
+
const head = Math.ceil((max - 1) / 2);
|
|
92
|
+
const tail = Math.floor((max - 1) / 2);
|
|
93
|
+
return `${value.slice(0, head)}…${value.slice(value.length - tail)}`;
|
|
94
|
+
}
|
|
95
|
+
/** A short, stable fingerprint of a whole set, so two approvals for DIFFERENT
|
|
96
|
+
* sets never read identically even when only a few members are shown.
|
|
97
|
+
*
|
|
98
|
+
* SHA-256, truncated to 128 bits. A 32-bit FNV was small enough to collide on
|
|
99
|
+
* purpose: two batches whose first three members matched could be given the
|
|
100
|
+
* same digest, and with only three members sampled the two prompts became
|
|
101
|
+
* indistinguishable — for two different permanent deletions. */
|
|
102
|
+
function digest(input) {
|
|
103
|
+
return createHash("sha256").update(input).digest("hex").slice(0, 32);
|
|
104
|
+
}
|
|
105
|
+
/** A stable serialisation of the WHOLE value, before anything is shortened for
|
|
106
|
+
* display. What the fingerprint must identify is the batch, not its preview. */
|
|
107
|
+
function canonical(value) {
|
|
108
|
+
const walk = (v) => {
|
|
109
|
+
if (Array.isArray(v))
|
|
110
|
+
return v.map(walk);
|
|
111
|
+
if (v !== null && typeof v === "object") {
|
|
112
|
+
// KEYS SORTED. `JSON.stringify` preserves insertion order, so the same
|
|
113
|
+
// batch written with its fields in a different order fingerprinted
|
|
114
|
+
// differently — an identity that changes for no reason is no identity.
|
|
115
|
+
const obj = v;
|
|
116
|
+
const out = {};
|
|
117
|
+
for (const k of Object.keys(obj).sort())
|
|
118
|
+
out[k] = walk(obj[k]);
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
return v;
|
|
122
|
+
};
|
|
123
|
+
try {
|
|
124
|
+
return JSON.stringify(walk(value)) ?? String(value);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return String(value);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** How many members of a list to name before the fingerprint stands in for the
|
|
131
|
+
* rest. */
|
|
132
|
+
const LIST_SAMPLE = 3;
|
|
133
|
+
/** Compact rendering of a highlighted value.
|
|
134
|
+
*
|
|
135
|
+
* A list reports its LENGTH, then a few members, then a fingerprint of the
|
|
136
|
+
* whole set. Length alone made two bulk deletes of completely different GUIDs
|
|
137
|
+
* produce the same prompt — the operator was approving a permanent deletion
|
|
138
|
+
* with nothing on screen that distinguished it from another one.
|
|
139
|
+
*/
|
|
140
|
+
/** `from → to [exact|group, language]`, or null when the value is not a
|
|
141
|
+
* redirect. Shared by the single and the bulk rendering: a bulk create's
|
|
142
|
+
* members went through the generic JSON path, so up to 500 GROUP rules over
|
|
143
|
+
* whole branches could read the same as 500 exact ones. */
|
|
144
|
+
function renderRedirect(value, max) {
|
|
145
|
+
if (value === null || typeof value !== "object")
|
|
146
|
+
return null;
|
|
147
|
+
const obj = value;
|
|
148
|
+
if (typeof obj.from !== "string" || typeof obj.to !== "string")
|
|
149
|
+
return null;
|
|
150
|
+
const options = obj.options;
|
|
151
|
+
const scope = options?.groupRedirect === true ? "group" : "exact";
|
|
152
|
+
// Bounded and neutralised like the paths: `language` went in raw, so it was
|
|
153
|
+
// both an injection point and a way to crowd out the rest of the line.
|
|
154
|
+
const language = typeof obj.language === "string" && obj.language.length > 0
|
|
155
|
+
? shorten(obj.language, 16)
|
|
156
|
+
: "all";
|
|
157
|
+
return `${shorten(obj.from, max)} → ${shorten(obj.to, max)} [${scope}, ${language}]`;
|
|
158
|
+
}
|
|
159
|
+
/** Widest blast radius first: a group rule carries every path under its own,
|
|
160
|
+
* and a rule with no language applies to every language. Sampling in order
|
|
161
|
+
* showed three exact rules while the global group ones sat behind a `+N`. */
|
|
162
|
+
function blastRadius(value) {
|
|
163
|
+
if (value === null || typeof value !== "object")
|
|
164
|
+
return 0;
|
|
165
|
+
const obj = value;
|
|
166
|
+
const options = obj.options;
|
|
167
|
+
const group = options?.groupRedirect === true ? 2 : 0;
|
|
168
|
+
const global = typeof obj.language === "string" && obj.language.length > 0 ? 0 : 1;
|
|
169
|
+
return group + global;
|
|
170
|
+
}
|
|
171
|
+
/** `2 group/all, 1 exact/fr` — what the batch DOES, over every member, in the
|
|
172
|
+
* space a sample cannot cover. */
|
|
173
|
+
function scopeTally(value) {
|
|
174
|
+
const counts = new Map();
|
|
175
|
+
for (const v of value) {
|
|
176
|
+
const rendered = renderRedirect(v, 1);
|
|
177
|
+
if (rendered === null)
|
|
178
|
+
return null;
|
|
179
|
+
const scope = rendered.slice(rendered.lastIndexOf("[") + 1, -1);
|
|
180
|
+
counts.set(scope, (counts.get(scope) ?? 0) + 1);
|
|
181
|
+
}
|
|
182
|
+
return [...counts.entries()].map(([k, n]) => `${n} ${k}`).join(", ");
|
|
183
|
+
}
|
|
184
|
+
function renderHighlight(rawKey, value) {
|
|
185
|
+
// A PARAMETER NAME IS CALLER-SUPPLIED TOO. The tail loop renders every key the
|
|
186
|
+
// call carries, declared or not.
|
|
187
|
+
const key = shorten(rawKey, 40);
|
|
188
|
+
if (Array.isArray(value)) {
|
|
189
|
+
const members = value.map((v) => {
|
|
190
|
+
const asRedirect = renderRedirect(v, 12);
|
|
191
|
+
if (asRedirect !== null)
|
|
192
|
+
return asRedirect;
|
|
193
|
+
return typeof v === "string" ? v : (JSON.stringify(v) ?? "");
|
|
194
|
+
});
|
|
195
|
+
const sample = value
|
|
196
|
+
.map((v, i) => ({ i, radius: blastRadius(v) }))
|
|
197
|
+
.sort((a, b) => b.radius - a.radius || a.i - b.i)
|
|
198
|
+
.slice(0, LIST_SAMPLE)
|
|
199
|
+
.map(({ i }) => shorten(members[i], 40))
|
|
200
|
+
.join(", ");
|
|
201
|
+
const rest = members.length - Math.min(LIST_SAMPLE, members.length);
|
|
202
|
+
const tally = scopeTally(value);
|
|
203
|
+
// THREE PARTS, NOT ONE. Joined into a single string, a long tally — eight
|
|
204
|
+
// distinct languages is enough — pushed the whole thing over the budget and
|
|
205
|
+
// `fitParts` skipped it entire: no count, no member, no fingerprint, and an
|
|
206
|
+
// operator approving an irreversible batch with nothing identifying it.
|
|
207
|
+
// Split, the core survives and only the detail is dropped.
|
|
208
|
+
return [
|
|
209
|
+
// Fingerprint over the ORIGINAL values, not the shortened renderings:
|
|
210
|
+
// `/sameX-A-different-Ytail` and `/sameX-B-different-Ytail` both render as
|
|
211
|
+
// `/sameX…Ytail`, so a digest taken after truncation gave two different
|
|
212
|
+
// destructive batches the same identity.
|
|
213
|
+
`${key}: ${members.length}× #${digest(canonical(value))}`,
|
|
214
|
+
...(tally !== null ? [`(${tally})`] : []),
|
|
215
|
+
`[${sample}${rest > 0 ? `, +${rest}` : ""}]`,
|
|
216
|
+
];
|
|
217
|
+
}
|
|
218
|
+
// A redirect reads as `from → to` WITH ITS SCOPE. Showing only the paths made
|
|
219
|
+
// an exact French-only rule and a global group rule over a whole branch
|
|
220
|
+
// produce the same prompt — and the second can make far more pages
|
|
221
|
+
// unreachable than the first.
|
|
222
|
+
const asRedirect = renderRedirect(value, 40);
|
|
223
|
+
if (asRedirect !== null)
|
|
224
|
+
return asRedirect;
|
|
23
225
|
let json;
|
|
24
226
|
try {
|
|
25
|
-
json = JSON.stringify(
|
|
227
|
+
json = JSON.stringify(value);
|
|
26
228
|
}
|
|
27
229
|
catch {
|
|
28
|
-
json = "<unserializable
|
|
230
|
+
json = "<unserializable>";
|
|
231
|
+
}
|
|
232
|
+
return `${key}: ${shorten(json)}`;
|
|
233
|
+
}
|
|
234
|
+
/** Join what fits, in order, marking that something was left out.
|
|
235
|
+
*
|
|
236
|
+
* SKIPS an oversized part rather than stopping at it: stopping meant one long
|
|
237
|
+
* value hid every fact after it, which is how `from` and `to` could vanish
|
|
238
|
+
* from a prompt entirely. Every part is already length-bounded, so skipping is
|
|
239
|
+
* the rare case, not the normal one. */
|
|
240
|
+
const OMISSION_MARKER = " \u00b7 \u2026";
|
|
241
|
+
function fitParts(parts, budget) {
|
|
242
|
+
const out = [];
|
|
243
|
+
let used = 0;
|
|
244
|
+
let dropped = false;
|
|
245
|
+
for (const part of parts) {
|
|
246
|
+
const cost = out.length === 0 ? part.length : part.length + 3;
|
|
247
|
+
// Room for the marker is reserved once something HAS been dropped — that is
|
|
248
|
+
// when it will certainly be appended. The reservation used to be the other
|
|
249
|
+
// way round, so after the first drop the remaining parts filled the budget
|
|
250
|
+
// to the brim and the final `slice` ate the marker: an approval that had
|
|
251
|
+
// silently omitted parameters read as complete, on destructive calls.
|
|
252
|
+
if (used + cost > budget - (dropped ? OMISSION_MARKER.length : 0)) {
|
|
253
|
+
dropped = true;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
out.push(part);
|
|
257
|
+
used += cost;
|
|
258
|
+
}
|
|
259
|
+
if (!dropped)
|
|
260
|
+
return out.join(" · ").slice(0, budget);
|
|
261
|
+
// Reserving as we go is not enough on its own: the drop can be the LAST
|
|
262
|
+
// part, after earlier ones already filled the budget. Give the marker its
|
|
263
|
+
// room by surrendering trailing parts — parts are ordered by what decides a
|
|
264
|
+
// refusal, so the least decisive go first, and a truthful "something is
|
|
265
|
+
// missing" outranks one more shown value.
|
|
266
|
+
let joined = out.join(" · ");
|
|
267
|
+
while (out.length > 0 && joined.length + OMISSION_MARKER.length > budget) {
|
|
268
|
+
out.pop();
|
|
269
|
+
joined = out.join(" · ");
|
|
29
270
|
}
|
|
30
|
-
if (
|
|
31
|
-
return
|
|
32
|
-
return `${
|
|
271
|
+
if (out.length === 0)
|
|
272
|
+
return "";
|
|
273
|
+
return `${joined}${OMISSION_MARKER}`.slice(0, budget);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* The approval description, guaranteed to satisfy the protocol.
|
|
277
|
+
*
|
|
278
|
+
* Ordered by what decides a refusal: the irreversible effect, the site the call
|
|
279
|
+
* actually lands on (the preview used to strip `siteId`, so on a multi-site key
|
|
280
|
+
* the operator never saw which tenant), then each highlighted parameter.
|
|
281
|
+
*/
|
|
282
|
+
export function buildApprovalDescription(toolName, params, defaultSiteId) {
|
|
283
|
+
const site = typeof params.siteId === "string" && params.siteId.length > 0
|
|
284
|
+
? params.siteId
|
|
285
|
+
: defaultSiteId || "<no site configured>";
|
|
286
|
+
const parts = [];
|
|
287
|
+
const effect = APPROVAL_EFFECTS[toolName];
|
|
288
|
+
if (effect !== undefined)
|
|
289
|
+
parts.push(effect.toUpperCase());
|
|
290
|
+
parts.push(`site ${site}`);
|
|
291
|
+
// Highlighted parameters first, in the table's order, then EVERYTHING ELSE
|
|
292
|
+
// the call carries. Without the tail, a gated tool with no table entry —
|
|
293
|
+
// every pre-existing one — would show the operator nothing but a site.
|
|
294
|
+
const highlighted = APPROVAL_HIGHLIGHTS[toolName] ?? [];
|
|
295
|
+
const seen = new Set(["siteId"]);
|
|
296
|
+
const push = (rendered) => {
|
|
297
|
+
if (Array.isArray(rendered))
|
|
298
|
+
parts.push(...rendered);
|
|
299
|
+
else
|
|
300
|
+
parts.push(rendered);
|
|
301
|
+
};
|
|
302
|
+
for (const key of highlighted) {
|
|
303
|
+
if (!(key in params))
|
|
304
|
+
continue;
|
|
305
|
+
seen.add(key);
|
|
306
|
+
push(renderHighlight(key, params[key]));
|
|
307
|
+
}
|
|
308
|
+
for (const [key, value] of Object.entries(params)) {
|
|
309
|
+
if (seen.has(key))
|
|
310
|
+
continue;
|
|
311
|
+
push(renderHighlight(key, value));
|
|
312
|
+
}
|
|
313
|
+
const body = fitParts(parts, APPROVAL_DESCRIPTION_MAX);
|
|
314
|
+
// fitParts already respects the budget; the guard is belt-and-braces so a
|
|
315
|
+
// future edit cannot reintroduce the failure this function exists to prevent.
|
|
316
|
+
return body.slice(0, APPROVAL_DESCRIPTION_MAX) || toolName;
|
|
317
|
+
}
|
|
318
|
+
/** The approval title, likewise bounded (protocol: 1–80). */
|
|
319
|
+
export function buildApprovalTitle(toolName) {
|
|
320
|
+
return `Wix: confirm ${toolName}`.slice(0, APPROVAL_TITLE_MAX);
|
|
33
321
|
}
|
|
34
322
|
/**
|
|
35
323
|
* Build a `before_tool_call` handler bound to a resolved Wix config.
|
|
@@ -42,18 +330,13 @@ export function createApprovalHook(config, logger) {
|
|
|
42
330
|
return undefined;
|
|
43
331
|
if (!config.approvalRequired.has(event.toolName))
|
|
44
332
|
return undefined;
|
|
45
|
-
const description =
|
|
46
|
-
"```json\n" +
|
|
47
|
-
previewParams(event.params) +
|
|
48
|
-
"\n```\n\n" +
|
|
49
|
-
"Approve to execute, deny to cancel. The call will deny automatically " +
|
|
50
|
-
"if no decision is made within 10 minutes.";
|
|
333
|
+
const description = buildApprovalDescription(event.toolName, event.params, config.defaultSiteId);
|
|
51
334
|
if (config.logLevel === "debug") {
|
|
52
335
|
logger.debug?.(`wix: requesting approval for ${event.toolName} (runId=${event.runId ?? "?"})`);
|
|
53
336
|
}
|
|
54
337
|
return {
|
|
55
338
|
requireApproval: {
|
|
56
|
-
title:
|
|
339
|
+
title: buildApprovalTitle(event.toolName),
|
|
57
340
|
description,
|
|
58
341
|
severity: "critical",
|
|
59
342
|
timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../../src/hooks/approval.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,EAAE;AACF,yEAAyE;AACzE,uEAAuE;AACvE,0EAA0E;AAC1E,iCAAiC;AACjC,EAAE;AACF,yEAAyE;AACzE,4BAA4B;AAC5B,uEAAuE;AACvE,kDAAkD;AAClD,yEAAyE;AACzE,gBAAgB;
|
|
1
|
+
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../../src/hooks/approval.ts"],"names":[],"mappings":"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"}
|