openclaw-brokerkit 0.1.0 → 0.2.1
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/README.md +128 -93
- package/dist/src/browser-session.js +7 -0
- package/dist/src/commands.js +8 -9
- package/dist/src/generated/openapi-operator-v1.js +5 -0
- package/dist/src/generated/operator-v1.js +14 -10
- package/dist/src/generated/operator-validators.js +2757 -0
- package/dist/src/http.js +92 -18
- package/dist/src/operator-v1.js +26 -80
- package/dist/src/revisions.js +108 -0
- package/dist/src/runtime.js +52 -17
- package/dist/ui/assets/index-BpyayXQz.js +144 -0
- package/dist/ui/index.html +1 -2
- package/package.json +24 -7
- package/dist/ui/assets/index-8w0_bcoC.js +0 -143
- package/dist/ui/assets/index-D8Ypbs3j.css +0 -1
package/README.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# OpenClaw BrokerKit
|
|
2
2
|
|
|
3
|
-
`openclaw-brokerkit` adds a provider-neutral
|
|
4
|
-
commands to OpenClaw. It connects to one or
|
|
5
|
-
|
|
6
|
-
executable plans.
|
|
3
|
+
`openclaw-brokerkit` is an OpenClaw plugin that adds a provider-neutral
|
|
4
|
+
Approvals tab and `/brokerkit` commands to OpenClaw. It connects to one or
|
|
5
|
+
more BrokerKit Operator V1 sources; the brokers remain authoritative and
|
|
6
|
+
retain all provider credentials and executable plans.
|
|
7
7
|
|
|
8
|
-
The minimum supported host is `openclaw@2026.7.1
|
|
8
|
+
The minimum supported host is `openclaw@2026.7.1`, the first stable
|
|
9
9
|
SDK used by this package that includes public tab descriptors.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Install an exact release from
|
|
14
14
|
[npm](https://www.npmjs.com/package/openclaw-brokerkit) through OpenClaw:
|
|
15
15
|
|
|
16
16
|
```sh
|
|
@@ -24,15 +24,16 @@ pnpm --filter openclaw-brokerkit build
|
|
|
24
24
|
openclaw plugins install --link ./plugins/openclaw
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Choose
|
|
27
|
+
## Choose a trust mode
|
|
28
28
|
|
|
29
|
-
- `direct` trusts the OpenClaw process with operator SecretRefs and enables
|
|
30
|
-
tab, background reconciliation, `/brokerkit` commands, and channel
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
- `direct` trusts the OpenClaw process with operator SecretRefs and enables
|
|
30
|
+
the tab, background reconciliation, `/brokerkit` commands, and channel
|
|
31
|
+
delivery.
|
|
32
|
+
- `delegated-web` packages only the tab UI and delegates authenticated
|
|
33
|
+
browser requests to a same-origin trusted backend. It gives OpenClaw no
|
|
34
|
+
broker credential and registers no command or background source service.
|
|
34
35
|
|
|
35
|
-
##
|
|
36
|
+
## Direct mode configuration
|
|
36
37
|
|
|
37
38
|
```json5
|
|
38
39
|
{
|
|
@@ -46,7 +47,7 @@ Choose one explicit trust mode:
|
|
|
46
47
|
{
|
|
47
48
|
id: "hf-primary",
|
|
48
49
|
label: "Hugging Face",
|
|
49
|
-
endpoint: "
|
|
50
|
+
endpoint: "https://hf-broker.example.com",
|
|
50
51
|
operatorCredential: {
|
|
51
52
|
source: "env",
|
|
52
53
|
provider: "default",
|
|
@@ -61,12 +62,34 @@ Choose one explicit trust mode:
|
|
|
61
62
|
}
|
|
62
63
|
```
|
|
63
64
|
|
|
64
|
-
Literal operator credentials are rejected.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
Literal operator credentials are rejected; `operatorCredential.source` is
|
|
66
|
+
`env`, `file`, or `exec`. Non-loopback endpoints require HTTPS. Each
|
|
67
|
+
`endpoint` is the broker's Operator V1 listener, not its agent listener. The
|
|
68
|
+
plugin stores only cursors, opaque handles, generic channel destinations, and
|
|
69
|
+
delivery bookkeeping under the OpenClaw service state directory.
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
## Commands
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
/brokerkit subscribe
|
|
75
|
+
/brokerkit unsubscribe
|
|
76
|
+
/brokerkit pending
|
|
77
|
+
/brokerkit show <handle>
|
|
78
|
+
/brokerkit approve <handle>
|
|
79
|
+
/brokerkit deny <handle>
|
|
80
|
+
/brokerkit revoke <handle>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Commands are registered only in direct mode. They require an authorized
|
|
84
|
+
sender and the `operator.approvals` scope. Requesters cancel their own
|
|
85
|
+
pending requests through the authenticated broker client API, not through
|
|
86
|
+
this operator surface. Subscriptions use OpenClaw's generic outbound adapter,
|
|
87
|
+
so this package contains no Telegram, Discord, Slack, or other
|
|
88
|
+
channel-specific implementation.
|
|
89
|
+
|
|
90
|
+
## Delegated web mode
|
|
91
|
+
|
|
92
|
+
For a deployment that treats OpenClaw as untrusted:
|
|
70
93
|
|
|
71
94
|
```json5
|
|
72
95
|
{
|
|
@@ -87,111 +110,123 @@ For a deployment that treats OpenClaw as untrusted, configure delegated web:
|
|
|
87
110
|
```
|
|
88
111
|
|
|
89
112
|
The trusted backend implements `brokerkit.io/delegated-web/v1`, authenticates
|
|
90
|
-
the human operator, and issues an in-memory decision token lasting no more
|
|
91
|
-
five minutes. The base path must be a normalized same-origin absolute
|
|
92
|
-
The OpenClaw gateway must set `gateway.controlUi.embedSandbox` to
|
|
93
|
-
the approval tab can run while retaining its opaque sandbox
|
|
94
|
-
|
|
95
|
-
sandbox.
|
|
113
|
+
the human operator, and issues an in-memory decision token lasting no more
|
|
114
|
+
than five minutes. The base path must be a normalized same-origin absolute
|
|
115
|
+
path. The OpenClaw gateway must set `gateway.controlUi.embedSandbox` to
|
|
116
|
+
`scripts` so the approval tab can run while retaining its opaque sandbox
|
|
117
|
+
origin; do not use `trusted`.
|
|
96
118
|
|
|
97
119
|
When OpenClaw is outside the credential trust boundary, the trusted backend
|
|
98
120
|
must serve the packaged `dist/ui` files at the registered UI path itself. A
|
|
99
|
-
framed popover may receive a server-enforced `read` or `decide` session
|
|
100
|
-
`read` session shows requests without action controls
|
|
101
|
-
the operator act directly in the frame. Hosts that use framed decisions
|
|
102
|
-
that the surrounding application controls the iframe's placement and
|
|
103
|
-
attempt UI redressing; use the navigation-only launcher below when that
|
|
104
|
-
must remain out of scope.
|
|
121
|
+
framed popover may receive a server-enforced `read` or `decide` session: a
|
|
122
|
+
`read` session shows requests without action controls, and a `decide` session
|
|
123
|
+
lets the operator act directly in the frame. Hosts that use framed decisions
|
|
124
|
+
accept that the surrounding application controls the iframe's placement and
|
|
125
|
+
can attempt UI redressing; use the navigation-only launcher below when that
|
|
126
|
+
threat must remain out of scope.
|
|
105
127
|
|
|
106
|
-
|
|
128
|
+
### Session injection
|
|
129
|
+
|
|
130
|
+
The host injects the short-lived delegated session into the sandboxed
|
|
131
|
+
response:
|
|
107
132
|
|
|
108
133
|
```html
|
|
109
134
|
<meta name="brokerkit-delegated-session" content="BASE64URL_SESSION_JSON" />
|
|
110
135
|
```
|
|
111
136
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
```text
|
|
117
|
-
{ type: "brokerkit.delegated-web.open", version: 1, nonce }
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
The host verifies the exact opaque-origin frame source and navigates the whole
|
|
121
|
-
browser tab to the trusted UI URL. The top-level response is unframeable and
|
|
122
|
-
contains a server-enforced decision session, so approval, denial, cancellation,
|
|
123
|
-
and revocation require a second explicit action in the trusted document.
|
|
124
|
-
|
|
125
|
-
A host that does not provide a framed inbox can inject this marker so the UI
|
|
126
|
-
renders only the secure-navigation launcher:
|
|
127
|
-
|
|
128
|
-
```html
|
|
129
|
-
<meta name="brokerkit-delegated-top-level" />
|
|
130
|
-
```
|
|
137
|
+
Token-bearing HTML responses must enforce `sandbox allow-scripts` in their
|
|
138
|
+
CSP, and immutable UI assets must be loadable by the opaque document (serve
|
|
139
|
+
them without a same-origin CORP restriction or set
|
|
140
|
+
`Cross-Origin-Resource-Policy: cross-origin`).
|
|
131
141
|
|
|
132
142
|
The content is a base64url-encoded `brokerkit.io/delegated-web/v1` session
|
|
133
|
-
object
|
|
134
|
-
closed payload:
|
|
143
|
+
object; the UI removes the element as soon as it reads it. Version 1 uses
|
|
144
|
+
this closed payload:
|
|
135
145
|
|
|
136
146
|
```json
|
|
137
147
|
{
|
|
138
148
|
"api_version": "brokerkit.io/delegated-web/v1",
|
|
139
|
-
"token": "opaque-short-lived-
|
|
140
|
-
"expires_at": "
|
|
149
|
+
"token": "opaque-short-lived-browser-session",
|
|
150
|
+
"expires_at": "<no more than five minutes from issue time, RFC3339>",
|
|
141
151
|
"access": "read",
|
|
142
152
|
"renewal_transport": "direct"
|
|
143
153
|
}
|
|
144
154
|
```
|
|
145
155
|
|
|
146
156
|
`access` is `read` or `decide`; the host enforces it on every endpoint.
|
|
147
|
-
`renewal_transport` is `direct`
|
|
148
|
-
`<basePath>/session`
|
|
149
|
-
|
|
150
|
-
|
|
157
|
+
`renewal_transport` is `direct` (the UI posts the current session to
|
|
158
|
+
`<basePath>/session`) or `parent` (the nonce-bound bridge below returns the
|
|
159
|
+
replacement session in memory). The host preserves the token's access when it
|
|
160
|
+
returns a replacement session.
|
|
151
161
|
|
|
152
|
-
|
|
153
|
-
delegated session endpoint directly may instead use this host-neutral bridge:
|
|
162
|
+
### Browser session transport
|
|
154
163
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
Every direct and delegated-web browser API call carries its raw session in
|
|
165
|
+
one fixed HTTP field:
|
|
166
|
+
|
|
167
|
+
```http
|
|
168
|
+
GET /trusted-host/api/brokerkit/snapshot HTTP/1.1
|
|
169
|
+
Origin: null
|
|
170
|
+
BrokerKit-Session: eyJ2ZXJzaW9uIjoxLCJhdWRpZW5jZSI6Ii4uLiJ9.signature
|
|
171
|
+
Accept: application/json
|
|
158
172
|
```
|
|
159
173
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
174
|
+
`BrokerKit-Session` has no authentication-scheme prefix. A host must reject
|
|
175
|
+
empty, repeated, combined, malformed, or oversized values; tokens are 32 to
|
|
176
|
+
4096 bytes of visible ASCII excluding comma and whitespace. The same field
|
|
177
|
+
protects `POST /session`, `GET /snapshot`, `GET /events`, request detail,
|
|
178
|
+
approval, denial, and revocation; direct-mode capabilities use it on
|
|
179
|
+
`/plugins/brokerkit/api/v1` as well.
|
|
180
|
+
|
|
181
|
+
Standard `Authorization` is reserved for the hosting edge and for
|
|
182
|
+
server-to-server Operator V1 clients. Browser clients never use it, and never
|
|
183
|
+
put a delegated session in cookies, query parameters, local storage, or
|
|
184
|
+
session storage. Tokens must not be logged, traced, reflected in errors,
|
|
185
|
+
copied into an Operator V1 request, or persisted by the host or UI.
|
|
186
|
+
|
|
187
|
+
Because the packaged UI has an opaque origin under the required scripts-only
|
|
188
|
+
sandbox, a delegated host must accept only the expected `Origin: null`,
|
|
189
|
+
return `Access-Control-Allow-Origin: null`, allow `BrokerKit-Session` and
|
|
190
|
+
`Content-Type` in preflight responses, allow only its fixed route methods,
|
|
191
|
+
and set `Vary: Origin` plus `Cache-Control: no-store`. It must not return
|
|
192
|
+
`Access-Control-Allow-Credentials: true`; delegated API requests deliberately
|
|
193
|
+
omit cookies.
|
|
194
|
+
|
|
195
|
+
### Secure-navigation launcher
|
|
165
196
|
|
|
166
|
-
|
|
197
|
+
With `access: "read"`, each actionable request renders a **Review securely**
|
|
198
|
+
button instead of decision controls. The button posts this navigation-only
|
|
199
|
+
message:
|
|
167
200
|
|
|
168
201
|
```text
|
|
169
|
-
|
|
170
|
-
/brokerkit unsubscribe
|
|
171
|
-
/brokerkit pending
|
|
172
|
-
/brokerkit show <handle>
|
|
173
|
-
/brokerkit approve <handle>
|
|
174
|
-
/brokerkit deny <handle>
|
|
175
|
-
/brokerkit cancel <handle>
|
|
176
|
-
/brokerkit revoke <handle>
|
|
202
|
+
{ type: "brokerkit.delegated-web.open", version: 1, nonce }
|
|
177
203
|
```
|
|
178
204
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
205
|
+
The host verifies the exact opaque-origin frame source and navigates the
|
|
206
|
+
whole browser tab to the trusted UI URL. The top-level response is
|
|
207
|
+
unframeable and contains a server-enforced decision session, so approval,
|
|
208
|
+
denial, and revocation require a second explicit action in the trusted
|
|
209
|
+
document.
|
|
183
210
|
|
|
184
|
-
|
|
211
|
+
A host that does not provide a framed inbox can inject this marker so the UI
|
|
212
|
+
renders only the secure-navigation launcher:
|
|
185
213
|
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
214
|
+
```html
|
|
215
|
+
<meta name="brokerkit-delegated-top-level" />
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Parent session bridge
|
|
219
|
+
|
|
220
|
+
When the parent application is trusted, a sandboxed tab that cannot call the
|
|
221
|
+
delegated session endpoint directly may instead use this host-neutral bridge:
|
|
222
|
+
|
|
223
|
+
```text
|
|
224
|
+
request: { type: "brokerkit.delegated-web.session.request", version: 1, nonce }
|
|
225
|
+
response: { type: "brokerkit.delegated-web.session.response", nonce, session }
|
|
192
226
|
```
|
|
193
227
|
|
|
194
|
-
The
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
228
|
+
The trusted parent must answer only requests from the embedded BrokerKit
|
|
229
|
+
frame and must bind each response to the supplied 128-bit nonce. `session` is
|
|
230
|
+
the same `brokerkit.io/delegated-web/v1` object returned by
|
|
231
|
+
`POST <basePath>/session`. The bridge is a BrokerKit interface; it contains
|
|
232
|
+
no host-product namespace or host-specific payload.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const BROWSER_SESSION_HEADER = "BrokerKit-Session";
|
|
2
|
+
export const MAX_BROWSER_SESSION_BYTES = 4096;
|
|
3
|
+
export function validBrowserSession(value) {
|
|
4
|
+
return (value.length >= 32 &&
|
|
5
|
+
value.length <= MAX_BROWSER_SESSION_BYTES &&
|
|
6
|
+
/^[\x21-\x2B\x2D-\x7E]+$/u.test(value));
|
|
7
|
+
}
|
package/dist/src/commands.js
CHANGED
|
@@ -36,8 +36,8 @@ export async function handleCommand(runtime, ctx) {
|
|
|
36
36
|
return { text: "Invalid BrokerKit command." };
|
|
37
37
|
const lines = runtime
|
|
38
38
|
.snapshot()
|
|
39
|
-
.requests.filter((request) => request.status === "pending")
|
|
40
|
-
.map((request) => `${request.handle} · ${request.
|
|
39
|
+
.requests.filter((request) => request.request.status === "pending")
|
|
40
|
+
.map((request) => `${request.handle} · ${request.source_label} · ${request.request.presentation.title}`);
|
|
41
41
|
return {
|
|
42
42
|
text: lines.length ? lines.join("\n") : "No pending BrokerKit requests.",
|
|
43
43
|
};
|
|
@@ -49,13 +49,13 @@ export async function handleCommand(runtime, ctx) {
|
|
|
49
49
|
.requests.find((item) => item.handle === handle);
|
|
50
50
|
if (command === "show")
|
|
51
51
|
return { text: request ? formatRequest(request) : "Request not found." };
|
|
52
|
-
if (["approve", "deny", "
|
|
52
|
+
if (["approve", "deny", "revoke"].includes(command)) {
|
|
53
53
|
if (!request)
|
|
54
54
|
return { text: "Request not found." };
|
|
55
55
|
try {
|
|
56
|
-
const updated = await runtime.decide(handle, command, request.revision, ctx.senderId);
|
|
56
|
+
const updated = await runtime.decide(handle, command, request.request.revision, ctx.senderId);
|
|
57
57
|
return {
|
|
58
|
-
text: `${capitalize(command)} committed for ${updated.presentation.title}.`,
|
|
58
|
+
text: `${capitalize(command)} committed for ${updated.request.presentation.title}.`,
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
61
|
catch (error) {
|
|
@@ -88,11 +88,10 @@ function capitalize(value) {
|
|
|
88
88
|
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
89
89
|
}
|
|
90
90
|
function formatRequest(request) {
|
|
91
|
-
const facts = request.presentation.facts?.map((fact) => `${fact.label}: ${fact.value}`) ??
|
|
92
|
-
[];
|
|
91
|
+
const facts = request.request.presentation.facts?.map((fact) => `${fact.label}: ${fact.value}`) ?? [];
|
|
93
92
|
return [
|
|
94
|
-
`${request.
|
|
95
|
-
request.presentation.summary ?? "",
|
|
93
|
+
`${request.source_label}: ${request.request.presentation.title}`,
|
|
94
|
+
request.request.presentation.summary ?? "",
|
|
96
95
|
...facts,
|
|
97
96
|
`Handle: ${request.handle}`,
|
|
98
97
|
]
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export const OPERATOR_V1_SCHEMA_SHA256 = "a4510c3d0e1ddf32dfb8e8bd684677ade3f3d6541912d42720af8c43ef11c2eb";
|
|
1
|
+
export const OPERATOR_V1_SCHEMA_SHA256 = "62065b7c9d09989c66379c6a81dd23f9ae468c7d1a859b69c88a1dee079aa6a6";
|
|
3
2
|
export const operatorV1 = {
|
|
4
3
|
apiVersion: "brokerkit.io/operator/v1",
|
|
5
4
|
statuses: [
|
|
@@ -11,7 +10,7 @@ export const operatorV1 = {
|
|
|
11
10
|
"consumed",
|
|
12
11
|
"revoked",
|
|
13
12
|
],
|
|
14
|
-
actions: ["approve", "deny", "
|
|
13
|
+
actions: ["approve", "deny", "revoke"],
|
|
15
14
|
risks: ["unknown", "low", "medium", "high", "critical"],
|
|
16
15
|
eventKinds: [
|
|
17
16
|
"request.created",
|
|
@@ -19,21 +18,26 @@ export const operatorV1 = {
|
|
|
19
18
|
"request.denied",
|
|
20
19
|
"request.canceled",
|
|
21
20
|
"request.expired",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
21
|
+
"grant.revoked",
|
|
22
|
+
"grant.reserved",
|
|
23
|
+
"grant.consumed",
|
|
24
|
+
"grant.released",
|
|
25
|
+
"execution.succeeded",
|
|
26
|
+
"execution.failed",
|
|
27
|
+
"execution.ambiguous",
|
|
25
28
|
],
|
|
26
29
|
errorCodes: [
|
|
27
30
|
"invalid_request",
|
|
28
31
|
"unauthorized",
|
|
29
32
|
"forbidden",
|
|
30
33
|
"not_found",
|
|
34
|
+
"method_not_allowed",
|
|
31
35
|
"revision_conflict",
|
|
32
|
-
"invalid_transition",
|
|
33
|
-
"constraint_exceeded",
|
|
34
36
|
"idempotency_conflict",
|
|
37
|
+
"constraint_exceeded",
|
|
38
|
+
"invalid_transition",
|
|
39
|
+
"invalid_decision_token",
|
|
35
40
|
"cursor_expired",
|
|
36
|
-
"rate_limited",
|
|
37
41
|
"temporarily_unavailable",
|
|
38
42
|
"internal_error",
|
|
39
43
|
],
|
|
@@ -52,6 +56,6 @@ export const operatorV1 = {
|
|
|
52
56
|
page: 100,
|
|
53
57
|
idempotencyKey: 200,
|
|
54
58
|
errorMessage: 500,
|
|
55
|
-
correlationId:
|
|
59
|
+
correlationId: 128,
|
|
56
60
|
},
|
|
57
61
|
};
|