@byokit/openclaw 0.5.0 → 0.6.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/CHANGELOG.md +39 -0
- package/README.md +174 -2
- package/dist/auth-store.js +1 -1
- package/dist/bridge.d.ts +10 -2
- package/dist/bridge.js +61 -14
- package/dist/browser/broker.d.ts +63 -0
- package/dist/browser/broker.js +990 -0
- package/dist/browser/host.d.ts +98 -0
- package/dist/browser/host.js +945 -0
- package/dist/browser/resume.d.ts +10 -0
- package/dist/browser/resume.js +23 -0
- package/dist/browser/store.d.ts +25 -0
- package/dist/browser/store.js +151 -0
- package/dist/browser/verify.d.ts +17 -0
- package/dist/browser/verify.js +38 -0
- package/dist/browser.d.ts +212 -0
- package/dist/browser.js +4 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.js +130 -0
- package/dist/day-usage.d.ts +76 -0
- package/dist/day-usage.js +104 -0
- package/dist/device.d.ts +32 -2
- package/dist/device.js +224 -4
- package/dist/engine-patches.d.ts +36 -0
- package/dist/engine-patches.js +320 -0
- package/dist/engine-status.d.ts +13 -0
- package/dist/engine-status.js +31 -0
- package/dist/engine.d.ts +13 -1
- package/dist/engine.js +99 -52
- package/dist/index.d.ts +6 -1
- package/dist/index.js +2 -0
- package/dist/kit.d.ts +29 -3
- package/dist/kit.js +324 -13
- package/dist/link.d.ts +5 -2
- package/dist/link.js +333 -4
- package/dist/routes.d.ts +19 -8
- package/dist/routes.js +44 -11
- package/dist/routes.json +3775 -356
- package/dist/runs.js +8 -2
- package/dist/signin.d.ts +2 -1
- package/dist/signin.js +103 -7
- package/dist/testing/browser.d.ts +50 -0
- package/dist/testing/browser.js +166 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/model-stub.d.ts +1 -0
- package/dist/testing/model-stub.js +3 -1
- package/dist/types.d.ts +50 -3
- package/dist/usage-boots.d.ts +8 -0
- package/dist/usage-boots.js +26 -0
- package/dist/usage.d.ts +51 -0
- package/dist/usage.js +61 -0
- package/dist/words.json +42 -0
- package/engine/OPENCLAW-LICENSE +24 -0
- package/engine/patches.json +59 -0
- package/engine/patches.schema.json +41 -0
- package/package.json +12 -4
- package/plugin/index.js +78 -5
- package/plugin/usage.js +125 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.6.1 (2026-10-04)
|
|
6
|
+
|
|
7
|
+
FIX: Let apps declare `appOwnedSessions.keyPrefixes` before Gateway startup so caller-requeued task sessions do not also receive an engine-started recovery turn. Preserve session history, policy gates, API-key session mapping and stock recovery for other namespaces.
|
|
8
|
+
FEAT: Add the internal pipe-only browser broker with fenced private targets, lease-bound live sessions and exact-origin site-data clearing. Sign-in handoff remains unprotected until parked-session protection is qualified.
|
|
9
|
+
FIX: Launch each broker's Chromium with its own short mode-0700 temp directory under the caller's temp directory, removed on close, instead of using the profile path as TMPDIR; deep app profile paths overflowed Chromium's singleton socket path and aborted startup.
|
|
10
|
+
FIX: A controlling viewer ignores frames from a previous target and drops input until the current private target has delivered its first image, so popup switches cannot map input with stale coordinates.
|
|
11
|
+
FIX: The broker's browser endpoint and `/json/version` now use Chromium's canonical `/devtools/browser/<id>?token=` shape, so the pinned engine's CDP discovery finds `/json/list` instead of failing with HTTP 404; token, host and origin guards are unchanged and the old bare path is refused.
|
|
12
|
+
FIX: Chromium's sandbox stays on; a launch refused for no usable sandbox (for example where unprivileged user namespaces are disabled) now fails with typed `BrowserSandboxUnavailable` instead of a generic failure, and is never retried unsandboxed. Startup stderr is used only in memory for that check.
|
|
13
|
+
FIX: Wire opt-in member browsers to the shipped bridge and stock engine, with a dead default profile, member-pinned browser actions, kit browser invalidation events and scoped browser-engine checks. Sign-in handoff remains `handoff-unprotected`; no recovery-protection or automatic resume redispatch claim is made.
|
|
14
|
+
SECURITY: Register the published awaited OpenClaw/Codex tool-result middleware, scrub owned broker capabilities before live model feedback, recheck session admission per result, and terminate on refusal or unavailable protection. Transcript hooks are supplementary; actual full privacy/recovery qualification remains pending.
|
|
15
|
+
FIX: Honor the stock allow/alsoAllow schema, create model-free audit sessions for effective-tool checks, and acknowledge redacted CDP profiles only against an unchanged exact host-owned endpoint and matching applied-source revisions.
|
|
16
|
+
FIX: Explicitly clear hostile node selection across the stock hook's shallow parameter merge; browser proof fixtures distinguish normal model completion from successful tool execution.
|
|
17
|
+
SECURITY: A definite failed sign-in resume no longer permits arbitrary recovery turns: replacement requires a fresh live kit registration matching the exact engine run id and session, revoked on release or bridge shutdown. Source controls cover parked and every unproved resume state; actual engine qualification remains pending. Reconnect keeps tool subsets but revokes submission authority, and waits for old Unix socket teardown before rebinding; shutdown cancels outstanding calls.
|
|
18
|
+
SECURITY: Browser use requires a closed explicit safe tool policy across every engine agent, account agent and delegate; unsafe and unknown effective tools refuse every browser, model-facing profile management/evaluation is blocked before app approval, and raw agent/plugin/config policy mutations require the guarded config path.
|
|
19
|
+
- FEAT: Portable types for the browser sign-in handoff and live view (`NeedSignIn`, `BrowserState`, `LiveViewState`, `BrowserHost`, `BrowserDevice` and friends) from `.` and `./device`, per spec 5.17. Types only: no runtime ships yet, and handoff stays refused until parked sessions are protected.
|
|
20
|
+
- Allow the internal browser host to attach newly created owned-member browsers and replace exact owned endpoints with stale-identity guards, private-target cleanup barriers and read-only versioned bindings for guarded configuration publication. Request bounded thumbnails through the owned viewer. Production sign-in handoff remains blocked pending qualification.
|
|
21
|
+
- Add the internal browser handoff host, durable settlement and conservative resume bookkeeping, with offline synthetic fixtures. Production sign-in remains blocked pending recovery-protection qualification.
|
|
22
|
+
- Add member-scoped browser sign-in actions, private browser live view and portable device helpers. Browser frames and lease capabilities travel on transient encrypted streams, outside durable link answer caches; control requires the host's immediate grant-revocation seam. Watching or reconnecting never dispatches a model run.
|
|
23
|
+
- FIX: Preserve browser reply and event order when messages arrive together, so page evaluation can receive its initial context instead of waiting indefinitely.
|
|
24
|
+
SECURITY: Ship a content-addressed bundled-engine patch manifest and the pinned OpenClaw MIT notice. Fully verified read-only sibling engine sets replace in-place installation; base and existing set bytes are never changed, even when shared Gateways are live. `KitState.patchSet` and `why: 'engine-patch'` expose provenance; rollback selects verified stock offline, while stock drift may need registry access. The initial patch set is empty.
|
|
25
|
+
- `getConfigKey` / `setConfigKey` read and write exactly one dotted config key in the file `prepare()` owns, so an app can narrow-read and restore a value (for example `skills.workshop.autonomous.mode`) around a boot without a whole `config.get`, whose result redacts token-bearing values. Only that key changes, the write is atomic in `prepare()`'s exact shape and happens only when the bytes change, and `undefined` removes the key.
|
|
26
|
+
FEAT: Add portable `readAgentDayUsage` / `AgentDayUsage` readings that combine fresh retained transcripts with durable Gateway Skill Workshop review charges. UTC/IANA windows, pending/interrupted/missing usage and conservative completeness let callers keep unknown budgets unknown. The original partial usage API is unchanged; other detached kinds and the worker bundle remain uncovered.
|
|
27
|
+
FIX: Persist detached Workshop review usage, including reported usage on failed outcomes, without double-counting memory flushes, recovery resumes or run results. Per-boot/month attempt counters, fsynced launch and clean-stop records, phase deduplication and bounded month reads preserve failure holes and crash incompleteness.
|
|
28
|
+
SECURITY: The bundled checksum-pinned Gateway Workshop patch and operator-read usage RPC write/read accounting identities, times and reported counters only, never prompts, outputs, tool arguments or credentials. Device reads stay member-scoped and never access host files; unknown costs and unverified billing identity are not guessed.
|
|
29
|
+
FIX: `stop()` now signals only the pids the kit itself spawned and never a process group, so shutdown no longer reaches processes the kit never started. Every spawn is recorded when it starts and dropped when it exits; the gateway is left to shut its own sessions down on its own SIGTERM.
|
|
30
|
+
- Unreleased source dependency metadata: pin @byokit/seal 0.3.0 and @byokit/relay 0.5.2; this package's existing version is not republished. Previously published consumer metadata remains unchanged.
|
|
31
|
+
- SECURITY: Sensitive subscription sign-in tokens stay out of sign-in views and results, including engine errors, links and codes after token entry.
|
|
32
|
+
- FIX: Subscription setup-token sign-in now answers sensitive wizard text steps from the existing paste channel, once per step, with cancellation and timeout preserved. API key (billed per use) entry still requires explicit selection.
|
|
33
|
+
|
|
34
|
+
## 0.6.0 (2026-10-02)
|
|
35
|
+
|
|
36
|
+
- Portable `readAgentUsage` / `agentUsageOf` expose explicit-agent UTC retained-transcript totals with raw responses, cache freshness and missing-price counters preserved. Coverage stays partial: stock-engine detached Workshop reviews are absent, and unavailable data never becomes zero; complete internal-turn accounting is not claimed.
|
|
37
|
+
- FIX: Host runs can reuse a caller-stable per-dispatch `idempotencyKey` after a lost connection instead of starting a second engine run. Omission keeps fresh UUIDs. Document the pinned engine's gateway-wide, bounded, in-memory cache and honest in-flight replay limits; this is not exactly-once across engine restarts.
|
|
38
|
+
|
|
39
|
+
FIX: Generate complete pinned route discovery: 91 auth choices and five cloud/CLI providers, including external plugin dependencies, plan-key entry, service grouping and corrected provider/billing labels. `routes()` computes readiness without reading credentials or installing plugins; legacy boolean `offer` is ready default subscriptions only, with `offerPolicy` retaining eligibility. Missing plugins, binaries, client registrations and choiceless wizard flows remain visible, not falsely usable. Explicit API/local/endpoint routes never become default billing; endpoint billing requires host input. Full discovery also reaches the existing phone/web link without hiding unavailable rows. Existing choice IDs, legacy explicit browser/code selectors, native Claude activation guards and ChatGPT pairing are retained; corrected manifest provider IDs and semantic `via` values replace inaccurate discovery labels. Listing an API or unavailable route never authorizes a sign-in or makes it a default.
|
|
40
|
+
|
|
41
|
+
- The README points to the shared account-route vocabulary (D18); pinned route discovery is not proof of additional implemented authentication flows.
|
|
42
|
+
- FIX: ChatGPT sign-in now interrupts abandoned wizard/paste waits on gateway disconnect and reconfirms a new, usable OAuth profile for the selected member through the reconnected engine. Recovery is bounded and fails closed for unknown, missing, pre-existing or unrelated credentials, cancellation and expiry. A synchronous cleanup error during reconnect no longer suppresses the terminal sign-in view.
|
|
43
|
+
|
|
5
44
|
## 0.5.0 (2026-10-01)
|
|
6
45
|
|
|
7
46
|
- Dependency update: pins @byokit/relay 0.5.1.
|
package/README.md
CHANGED
|
@@ -25,6 +25,26 @@ under your `stateDir` (a few minutes, needs the network once), and later starts
|
|
|
25
25
|
phone page that pairs, signs in with ChatGPT, runs and answers approvals, see
|
|
26
26
|
[`examples/openclaw-kit`](../../examples/openclaw-kit).
|
|
27
27
|
|
|
28
|
+
## Bundled engine provenance
|
|
29
|
+
|
|
30
|
+
`engine/patches.json` declares the exact upstream tarball integrity, commit, unique edits and before/after file
|
|
31
|
+
hashes. The Gateway Workshop review patch writes content-free, durable usage facts only when the kit sets its
|
|
32
|
+
accounting environment. The separately bundled worker and all other detached kinds remain uncovered.
|
|
33
|
+
The full upstream MIT notice ships in [engine/OPENCLAW-LICENSE](engine/OPENCLAW-LICENSE).
|
|
34
|
+
|
|
35
|
+
Each `prepare()` verifies the **entire** selected engine tree, including unpatched files. The kit installs stock
|
|
36
|
+
into a private temporary tree, clones verified stock for a changed set, then publishes read-only sibling trees
|
|
37
|
+
under `<engineDir>.sets/`. It never patches, reinstalls or deletes the base `<engineDir>/node_modules` or any
|
|
38
|
+
published set, even when a shared Gateway is running. `doctorContext().entry` points at the selected set after
|
|
39
|
+
prepare; `<stateDir>/openclaw/engine-set` records that selection. `kit.state.patchSet` is its content-addressed
|
|
40
|
+
patch id (`null` after a provenance failure); a refusal is `why: 'engine-patch'` with a typed error code.
|
|
41
|
+
|
|
42
|
+
Rollback selects a verified stock or older set; known sets and offline clones require no registry access.
|
|
43
|
+
Drift leaves the old bytes untouched and builds a replacement sibling; rebuilding stock may need the registry.
|
|
44
|
+
Pre-change kits still run the untouched base install. No garbage collection: each retained set can occupy about
|
|
45
|
+
889 MB on this pin (reflinks may reduce physical storage). Whole-tree verification also adds preparation I/O;
|
|
46
|
+
read-only modes protect against accidental writes, not a malicious host app that owns the files.
|
|
47
|
+
|
|
28
48
|
## Quickstart
|
|
29
49
|
|
|
30
50
|
Start the engine, add a member, sign them in with a subscription, run a message (typechecked; running it installs
|
|
@@ -56,6 +76,32 @@ console.log(end);
|
|
|
56
76
|
await kit.stop();
|
|
57
77
|
```
|
|
58
78
|
|
|
79
|
+
For a host run that may be retried after a lost connection, mint and persist an action key **once**, then reuse
|
|
80
|
+
that key and the same run inputs:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import type { OpenClawKit } from '@byokit/openclaw';
|
|
84
|
+
|
|
85
|
+
async function dispatch(kit: OpenClawKit, taskId: string, attempt: number, message: string) {
|
|
86
|
+
const actionKey = `task:${taskId}:attempt:${attempt}:${crypto.randomUUID()}`;
|
|
87
|
+
// Persist actionKey with this dispatch before calling; a new action/attempt gets a new nonce.
|
|
88
|
+
const spec = { member: 'umer', sessionKey: `agent:umer:task:${taskId}`, message, idempotencyKey: actionKey };
|
|
89
|
+
return kit.run(spec); // reconnect retry: kit.run(spec), not another dispatch()/new actionKey
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Omitting `idempotencyKey` preserves a fresh UUID per call. Supplied keys are non-empty strings passed unchanged.
|
|
94
|
+
**Not exactly-once:** engine 2026.8.1 caches `agent` keys gateway-wide, across agents and sessions, without comparing
|
|
95
|
+
inputs. A collision silently returns the first run's result/error; a task id alone is not a safe key. Reconnects
|
|
96
|
+
retain the in-memory cache; engine process restarts lose it. Inactive entries expire after five minutes (60-second
|
|
97
|
+
cleanup tick), or earlier under the 1,000-entry cache limit; active and pending accepted runs are exempt.
|
|
98
|
+
An in-flight replay waits on the original run but cannot replay old events or subscribe to its final response:
|
|
99
|
+
text may be a capped 4096-character terminal snapshot, usage stays absent, and capped JSON may fail schema
|
|
100
|
+
validation. A completed cached replay can return full text/usage. The helper never sends another `agent` request
|
|
101
|
+
just to read a final result: on cache eviction that would start another run. Apps still own durable task/effect
|
|
102
|
+
recovery. Typed `kit.call('agent', ...)` remains exact upstream pass-through. Details and real-engine proof are
|
|
103
|
+
in [the run contract](../../docs/runtime-kits.md#58-runs-members-and-streams).
|
|
104
|
+
|
|
59
105
|
Device approval can take longer than two minutes. The kit waits through the engine's advertised code lifetime
|
|
60
106
|
(`expiresInMinutes` on the pin, or `expires_in` seconds when supplied), including progress pulls. If the engine
|
|
61
107
|
supplies no lifetime, it owns the deadline. Ordinary wizard requests keep their 120-second timeout.
|
|
@@ -63,6 +109,13 @@ supplies no lifetime, it owns the deadline. Ordinary wizard requests keep their
|
|
|
63
109
|
`done` returns a typed `SignInView`: `why: 'expired'` for an expired code, `why: 'declined'` for cancellation,
|
|
64
110
|
with a plain sentence in `error`. `SignInOptions` is exported from the host entry.
|
|
65
111
|
|
|
112
|
+
For a subscription setup-token or any sensitive wizard text step, pass the token once through the same
|
|
113
|
+
`signIn.paste(token)` channel and clear the input afterwards. The kit displays only a fixed entry label;
|
|
114
|
+
subsequent wizard errors, links and codes are withheld, and failures use a fixed message rather than
|
|
115
|
+
engine text that could echo the token. It is sent only as this member's wizard answer, never in views,
|
|
116
|
+
results or kit logs. Ordinary browser paste and typed `kit.call('wizard.next', …)` pass-through are unchanged.
|
|
117
|
+
API key (billed per use) entry still requires explicit selection; there is no subscription-to-key fallback.
|
|
118
|
+
|
|
66
119
|
To choose which of a member's accounts a run uses, pass `model: 'provider/model'` in the run spec (for example
|
|
67
120
|
`'openai/gpt-5.1'`, where `provider` is an id `kit.providers(member)` lists). That provider is the one called and billed for this run only, with no fallback to another
|
|
68
121
|
provider or model. If the member isn't signed in to it, the run ends `{ ok: false, kind: 'signed-out' }` and the
|
|
@@ -94,6 +147,71 @@ ends ok carries `usage` (the engine's token total for the run, and `costUsd` whe
|
|
|
94
147
|
`openclawDevice(link).run(message, o)` takes the same options over the link, and `state()` adds the kit and engine
|
|
95
148
|
versions and the providers the device's member is signed in to.
|
|
96
149
|
|
|
150
|
+
### Observed per-agent ledger usage (not complete engine spend)
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { readAgentUsage } from '@byokit/openclaw/usage'; // portable; kit or device client
|
|
154
|
+
import type { OpenClawKit } from '@byokit/openclaw';
|
|
155
|
+
|
|
156
|
+
async function showUsage(kit: OpenClawKit) {
|
|
157
|
+
const reading = await readAgentUsage(kit, 'umer', { startDate: '2026-10-02', endDate: '2026-10-02' });
|
|
158
|
+
// Caller decides whether partial coverage is acceptable; never turn unavailable totals into zero.
|
|
159
|
+
if (reading.state === 'available') console.log(reading.totals);
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`agentUsageOf(raw, member, window)` also normalizes an existing explicitly agent-scoped UTC response. Full raw
|
|
164
|
+
RPC data is retained; existing typed `call` pass-through is unchanged. Windows are inclusive UTC calendar days.
|
|
165
|
+
`receivedAt`, result-assembly `updatedAt` and `cache.refreshedAt` are separate: receiving cached data does not
|
|
166
|
+
refresh it. The engine response cache lasts 30 seconds; pending/stale/unknown data leaves `totals` absent.
|
|
167
|
+
An absent agent row is unavailable, not proof of zero usage. Reset/deletion/retention can reduce counters.
|
|
168
|
+
|
|
169
|
+
**Coverage is always `retained-transcripts-only`.** Stock 2026.8.1's detached Skill Workshop reviews are omitted
|
|
170
|
+
from its ledger; actual crash-recovery resumes and threshold memory flushes are counted through persisted
|
|
171
|
+
transcripts, never through a second side total. This reader cannot enforce a complete all-turn budget. The app owns budget,
|
|
172
|
+
share and unavailable/partial-data policy. Do not add run results to these totals: they already overlap. Engine
|
|
173
|
+
cost fields are price counters, not a bill or subscription quota; zero cost with `missingCostEntries > 0` is
|
|
174
|
+
unknown cost, and tokens never establish subscription plan weights. No billing conversion is performed.
|
|
175
|
+
|
|
176
|
+
### Day readings including Gateway Workshop reviews
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { readAgentDayUsage } from '@byokit/openclaw/day-usage'; // also exported by . and ./device
|
|
180
|
+
|
|
181
|
+
async function dayBudget(kitOrDevice: Parameters<typeof readAgentDayUsage>[0]) {
|
|
182
|
+
const startMs = Date.parse('2026-10-02');
|
|
183
|
+
const day = await readAgentDayUsage(kitOrDevice, 'umer', {
|
|
184
|
+
startMs, endMs: startMs + 86_400_000 - 1, mode: 'utc',
|
|
185
|
+
});
|
|
186
|
+
return day.knownTotalTokens !== undefined && day.knownTotalTokens > 190 ? 'over'
|
|
187
|
+
: day.complete ? 'under' : 'unknown'; // policy is the caller's
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
This additive reader leaves `readAgentUsage` unchanged. Coverage is **retained transcripts + Gateway Workshop
|
|
192
|
+
reviews**, not all engine spend. Reviews crossing midnight land on their ended day. UTC and IANA
|
|
193
|
+
`{ mode: 'time-zone', timeZone: 'America/New_York' }` apply to both terms; Gateway-local zones are refused.
|
|
194
|
+
The engine transcript RPC reports calendar days: use complete inclusive days in that zone. A partial-day window
|
|
195
|
+
leaves the transcript term unavailable rather than inventing a millisecond total.
|
|
196
|
+
|
|
197
|
+
Review facts carry only run/session/agent/model/profile ids, times, outcomes and reported tokens. Their cost is
|
|
198
|
+
always `missing`; billing stays `unknown` without verified selected-route identity, never inferred from a provider.
|
|
199
|
+
Missing usage is `reported-missing`, a live start is `pending`, and an old boot's start is `interrupted`; none means
|
|
200
|
+
zero. Facts deduplicate by charge and phase. `knownTotalTokens` is the sum of reported counters only and is absent
|
|
201
|
+
when either source is unavailable. `complete: false` is a budget-policy unknown, not permission to proceed.
|
|
202
|
+
|
|
203
|
+
The append-only `openclaw/usage/` ledger survives session reset, engine replacement and restart. Start records are
|
|
204
|
+
fsynced before spawning, using an accounting UUID separate from unchanged pid/start-time ownership. Month attempt
|
|
205
|
+
counters and failures survive clean stop; holes, missing counters, corrupt lines and overlapping crash boots keep
|
|
206
|
+
windows incomplete permanently. Reads open only requested UTC months plus boot records. No rotation yet.
|
|
207
|
+
Device reads use the operator-read plugin RPC, never host files; the link adapter restricts it to the granted member.
|
|
208
|
+
As with the existing transcript reader, the app must explicitly allow member-scoped `sessions.usage` pass-through.
|
|
209
|
+
|
|
210
|
+
Uncovered: worker-bundle work, skill collection review, history scans, slug generation, active-memory recall,
|
|
211
|
+
out-of-turn compaction and one-shot helper completions. Do not add `RunEnd.usage`, memory flush or recovery totals
|
|
212
|
+
again: they already overlap retained transcripts. Offline scripted-provider tests verify seven Gateway accounting
|
|
213
|
+
cases; they do not establish live subscription quota, provider bills or universal coverage.
|
|
214
|
+
|
|
97
215
|
For a validated JSON answer, pass a literal `schema`. The result's `data` is inferred from that schema:
|
|
98
216
|
|
|
99
217
|
```ts
|
|
@@ -198,7 +316,7 @@ Entries:
|
|
|
198
316
|
|
|
199
317
|
| Export | What it does |
|
|
200
318
|
| --- | --- |
|
|
201
|
-
| `OpenClawKit` (`.`) | Prepares, starts, supervises and stops the engine. `call` / `callDynamic` pass through to the Gateway, `onEvent` listens. Helpers: `ensureMember`, `routes`, `providers`, `signedIn`, `signIn`, `signOut`, `migrateRetainedLogin`, `confirmRetainedLogin`, `toolNames`, `run`, `steer`, `abort`, `approvals`, `onApproval`, `decide`, `allowOnce`, `disallowOnce`, `patchConfig`, `memoryLimited`, `doctorContext` |
|
|
319
|
+
| `OpenClawKit` (`.`) | Prepares, starts, supervises and stops the engine. `call` / `callDynamic` pass through to the Gateway, `onEvent` listens. Helpers: `ensureMember`, `routes`, `providers`, `signedIn`, `signIn`, `signOut`, `migrateRetainedLogin`, `confirmRetainedLogin`, `toolNames`, `run`, `steer`, `abort`, `approvals`, `onApproval`, `decide`, `allowOnce`, `disallowOnce`, `patchConfig`, `getConfigKey`, `setConfigKey`, `memoryLimited`, `doctorContext` |
|
|
202
320
|
| `ENGINE_VERSION`, `PROTOCOL_VERSION`, `OPERATOR_SCOPES` (`.`) | The pinned engine version, its protocol and the operator scopes the kit connects with |
|
|
203
321
|
| `KitOptions`, `RunSpec`, `RunEvent`, `RunEnd`, `RunUsage`, `PlanWindow`, `Route`, `Approval`, `Decision`, `ToolSpec`, `ToolHost`, `KitState`, ... (`.`) | Public types (docs/runtime-kits.md §5.2) |
|
|
204
322
|
| `openclawDevice(link)` (`./device`) | Portable client: state, routes, sign-in, runs, steer, abort, approvals, events, sealed notices, pass-through `call` |
|
|
@@ -208,6 +326,58 @@ Entries:
|
|
|
208
326
|
| `serve(o)` (`./link`) | Binds the link host per reach and returns its URLs |
|
|
209
327
|
| `fakeGateway`, `openclawContract`, `startModelStub`, `useModelStub` (`./testing`) | In-memory Gateway, the contract suite and the scripted model for tests |
|
|
210
328
|
|
|
329
|
+
## Owned browser (unqualified handoff)
|
|
330
|
+
|
|
331
|
+
`browser: { executablePath, members }` opts into isolated member Chromium profiles. It requires a closed
|
|
332
|
+
explicit tool allowlist and keeps `request_sign_in` refused: protected production handoff is not qualified.
|
|
333
|
+
Public live view/thumbnail APIs are model-free; source tests alone do not prove private/restart protection.
|
|
334
|
+
The opt-in physical fixture retains raw JPEG/control inputs before decoding, pipe chunk order, complete
|
|
335
|
+
synthetic provider bodies and authoritative SQLite transcripts, including failures.
|
|
336
|
+
|
|
337
|
+
## One config key, narrowed
|
|
338
|
+
|
|
339
|
+
`patchConfig` is the only whole-config writer, and it can only start from `config.get`, whose result redacts
|
|
340
|
+
token-bearing values: an app cannot read one key through it and write it back unchanged. For a single key, use the
|
|
341
|
+
narrow pair over the config file the engine loads at boot:
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
import { OpenClawKit } from '@byokit/openclaw';
|
|
345
|
+
|
|
346
|
+
const kit = new OpenClawKit({ stateDir: './openclaw-state' });
|
|
347
|
+
|
|
348
|
+
kit.getConfigKey('skills.workshop.autonomous.mode'); // value, or undefined
|
|
349
|
+
kit.setConfigKey('skills.workshop.autonomous.mode', 'off'); // returns the value it replaced
|
|
350
|
+
kit.setConfigKey('skills.workshop.autonomous.mode', undefined); // remove it again
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
One dotted path of `[A-Za-z0-9_-]` segments; prototype-reaching segments are refused. Only that key changes (every
|
|
354
|
+
other key keeps its order, an unchanged value writes no bytes, and removing a key takes the empty objects it created
|
|
355
|
+
back out), the write is atomic in `prepare()`'s exact shape, and the engine applies it at its next boot. Narrow a key
|
|
356
|
+
your app does **not** also pass in `KitOptions.config`: that option is merged over the saved file on every boot and
|
|
357
|
+
wins. Don't interleave a narrow set with `patchConfig`, which rewrites the whole config through the Gateway.
|
|
358
|
+
|
|
359
|
+
## App-owned task recovery
|
|
360
|
+
|
|
361
|
+
If your app requeues interrupted tasks itself, declare their namespaces **before starting the kit**:
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
import { OpenClawKit } from '@byokit/openclaw';
|
|
365
|
+
|
|
366
|
+
const kit = new OpenClawKit({
|
|
367
|
+
stateDir: './openclaw-state',
|
|
368
|
+
appOwnedSessions: { keyPrefixes: ['agent:m1:crewhouse:'] },
|
|
369
|
+
// tools and host as usual
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Matching task sessions are excluded from the bundled engine's automatic restart continuation; your app
|
|
374
|
+
continues the same session key with `kit.run`. Prefixes must start with `agent:<member>:` and must not cover
|
|
375
|
+
that member's `main` key. The kit also covers a public member's isolated API-key session rewrite. Omitted/empty
|
|
376
|
+
keeps stock recovery, including its existing retry accounting; no histories or recovery markers are deleted,
|
|
377
|
+
no tool gate is bypassed, and cron behavior is unchanged. Changing ownership requires an engine restart.
|
|
378
|
+
This option does not decide whether an interrupted external action is safe to retry; your app still owns
|
|
379
|
+
that decision. It needs the published kit's bundled engine patch, not a separately installed stock CLI.
|
|
380
|
+
|
|
211
381
|
## Phones and browsers
|
|
212
382
|
|
|
213
383
|
The host side serves member-checked ops over `@byokit/link`; the phone or browser uses the portable client:
|
|
@@ -243,6 +413,8 @@ The kit writes only under the `stateDir` the app passes. It spawns the engine wi
|
|
|
243
413
|
(never `process.env`), reads no environment variables except `PATH` to find `npm`, and never bills an API behind the
|
|
244
414
|
person's back (memory search is never a paid provider).
|
|
245
415
|
|
|
416
|
+
Route data follows the shared account-route vocabulary (D18 in [`docs/runtime-kits.md`](../../docs/runtime-kits.md#21-account-routes-d18)): subscription routes are offered by default, and every other billing is used only when named. The full table of the pinned engine's auth choices is planned work; `routes()` returns today's table.
|
|
417
|
+
|
|
246
418
|
## Credential sealing and threat model
|
|
247
419
|
|
|
248
420
|
The pinned engine has no supported hook for sealing OAuth profile writes. Its `auth-profiles` loader stores
|
|
@@ -342,6 +514,6 @@ frozen (docs/runtime-kits.md §5).
|
|
|
342
514
|
|
|
343
515
|
## License
|
|
344
516
|
|
|
345
|
-
Apache-2.0. See [LICENSE](LICENSE)
|
|
517
|
+
Apache-2.0. See [LICENSE](LICENSE); the bundled engine's MIT text is [engine/OPENCLAW-LICENSE](engine/OPENCLAW-LICENSE).
|
|
346
518
|
|
|
347
519
|
Claude: `anthropic-cli` uses your own unmodified Claude Code login on this machine (provider `claude-cli`, plugin `anthropic`; subscription billing), with login kept in Claude Code under the kit’s isolated `HOME=<stateDir>/openclaw/home` and `CLAUDE_CONFIG_DIR=<stateDir>/openclaw/home/.claude`; sign in there with `claude auth login`, then call `signIn`. Activation live-tests the route; native sign-out is through Claude Code. `apiKey` is explicit API billing (“API key (billed per use)”, `offer: false`): `signIn` asks for the key through `paste`, then engine activation verifies it. The kit never imports Claude credentials or implements direct Claude.ai OAuth; [Anthropic’s terms](https://code.claude.com/docs/en/legal-and-compliance#authentication-and-credential-use) apply.
|
package/dist/auth-store.js
CHANGED
|
@@ -252,7 +252,7 @@ export class AuthStore {
|
|
|
252
252
|
removeMarker(this.restoring);
|
|
253
253
|
}
|
|
254
254
|
async archives() {
|
|
255
|
-
const ignored = new Set([resolve(this.o.engineDir), ...['npm-cache', 'install-home', 'tmp', 'plugin', 'workspaces'].map((d) => resolve(this.o.root, d))]);
|
|
255
|
+
const ignored = new Set([resolve(this.o.engineDir), resolve(`${this.o.engineDir}.sets`), ...['npm-cache', 'install-home', 'tmp', 'plugin', 'workspaces'].map((d) => resolve(this.o.root, d))]);
|
|
256
256
|
const walk = async (dir) => {
|
|
257
257
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
258
258
|
const path = join(dir, entry.name);
|
package/dist/bridge.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export declare function writePlugin(dir: string, o: {
|
|
|
24
24
|
tools: ToolSpec[];
|
|
25
25
|
paramPrefix: string;
|
|
26
26
|
gateBuiltins: boolean;
|
|
27
|
+
browser?: boolean;
|
|
27
28
|
}): void;
|
|
28
29
|
export declare class Bridge {
|
|
29
30
|
private readonly path;
|
|
@@ -33,7 +34,11 @@ export declare class Bridge {
|
|
|
33
34
|
private readonly approvalTimeoutMs;
|
|
34
35
|
private readonly onAsk;
|
|
35
36
|
private readonly onAskGone;
|
|
37
|
+
private readonly beforeAgentRun?;
|
|
38
|
+
private readonly browserCapabilities?;
|
|
36
39
|
private server?;
|
|
40
|
+
private closing;
|
|
41
|
+
private readonly sockets;
|
|
37
42
|
private readonly runs;
|
|
38
43
|
private readonly live;
|
|
39
44
|
private readonly permits;
|
|
@@ -49,11 +54,14 @@ export declare class Bridge {
|
|
|
49
54
|
approvalTimeoutMs: number;
|
|
50
55
|
onAsk(a: Approval): void;
|
|
51
56
|
onAskGone(id: string): void;
|
|
57
|
+
beforeAgentRun?: (key: string, runId?: string) => Promise<boolean>;
|
|
58
|
+
browserCapabilities?: () => readonly string[];
|
|
52
59
|
});
|
|
53
60
|
start(): Promise<void>;
|
|
54
61
|
stop(): void;
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
isRegisteredRun(key: string, runId?: string): boolean;
|
|
63
|
+
/** Register one run; release revokes only this exact registration, even when sessions overlap. */
|
|
64
|
+
register(run: RunRef, tools?: readonly string[], runId?: string): () => void;
|
|
57
65
|
unregister(sessionKey: string): void;
|
|
58
66
|
allowOnce(rule: {
|
|
59
67
|
keyPrefix: string;
|
package/dist/bridge.js
CHANGED
|
@@ -41,7 +41,7 @@ export function writePlugin(dir, o) {
|
|
|
41
41
|
id: o.id,
|
|
42
42
|
name: 'BYOKit bridge',
|
|
43
43
|
activation: { onStartup: true },
|
|
44
|
-
contracts: { tools: o.tools.map((t) => t.name) },
|
|
44
|
+
contracts: { tools: o.tools.map((t) => t.name), ...(o.browser ? { agentToolResultMiddleware: ['openclaw', 'codex'] } : {}) },
|
|
45
45
|
configSchema: { type: 'object', additionalProperties: false },
|
|
46
46
|
};
|
|
47
47
|
const table = {
|
|
@@ -50,6 +50,7 @@ export function writePlugin(dir, o) {
|
|
|
50
50
|
permitParam,
|
|
51
51
|
// true: engine builtins (web_fetch, memory, ...) are gated too, not only the app's tools.
|
|
52
52
|
gateBuiltins: o.gateBuiltins,
|
|
53
|
+
...(o.browser ? { browser: true } : {}),
|
|
53
54
|
tools: o.tools.map((t) => ({ name: t.name, description: t.description, parameters: t.parameters })),
|
|
54
55
|
};
|
|
55
56
|
for (const [file, data] of [['openclaw.plugin.json', manifest], ['tools.json', table]]) {
|
|
@@ -67,7 +68,11 @@ export class Bridge {
|
|
|
67
68
|
approvalTimeoutMs;
|
|
68
69
|
onAsk;
|
|
69
70
|
onAskGone;
|
|
71
|
+
beforeAgentRun;
|
|
72
|
+
browserCapabilities;
|
|
70
73
|
server;
|
|
74
|
+
closing = Promise.resolve();
|
|
75
|
+
sockets = new Set();
|
|
71
76
|
runs = new Map();
|
|
72
77
|
// Every live registration per session key, each with its run's subset of the app's tools (RunSpec.tools; none: all
|
|
73
78
|
// of them). Runs sharing a key share the narrowest: a tool must be in every live subset.
|
|
@@ -85,18 +90,28 @@ export class Bridge {
|
|
|
85
90
|
this.approvalTimeoutMs = Math.min(o.approvalTimeoutMs, MAX_APPROVAL_TIMEOUT_MS);
|
|
86
91
|
this.onAsk = o.onAsk;
|
|
87
92
|
this.onAskGone = o.onAskGone;
|
|
93
|
+
this.beforeAgentRun = o.beforeAgentRun;
|
|
94
|
+
this.browserCapabilities = o.browserCapabilities;
|
|
88
95
|
}
|
|
89
|
-
start() {
|
|
96
|
+
async start() {
|
|
90
97
|
if (this.server)
|
|
91
|
-
return
|
|
98
|
+
return;
|
|
92
99
|
this.stopped = false;
|
|
93
|
-
|
|
100
|
+
await this.closing;
|
|
101
|
+
if (this.stopped || this.server)
|
|
102
|
+
return;
|
|
103
|
+
// Old listener teardown must finish before it can unlink a newly rebound Unix socket.
|
|
94
104
|
rmSync(this.path, { force: true });
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
this.
|
|
99
|
-
|
|
105
|
+
const server = createServer(socket => {
|
|
106
|
+
this.sockets.add(socket);
|
|
107
|
+
socket.once('close', () => this.sockets.delete(socket));
|
|
108
|
+
this.serve(socket);
|
|
109
|
+
});
|
|
110
|
+
this.server = server;
|
|
111
|
+
await new Promise((resolve, reject) => {
|
|
112
|
+
server.once('error', reject);
|
|
113
|
+
server.listen(this.path, () => {
|
|
114
|
+
server.removeListener('error', reject);
|
|
100
115
|
resolve();
|
|
101
116
|
});
|
|
102
117
|
});
|
|
@@ -110,14 +125,28 @@ export class Bridge {
|
|
|
110
125
|
this.onAskGone(id);
|
|
111
126
|
}
|
|
112
127
|
this.parked.clear();
|
|
113
|
-
|
|
128
|
+
// Tool subsets survive reconnect (B3); submission authority does not survive a lost engine transport.
|
|
129
|
+
for (const entries of this.live.values())
|
|
130
|
+
for (const entry of entries)
|
|
131
|
+
entry.runId = undefined;
|
|
132
|
+
// Preserve already-ended refusal replies; abort outstanding calls through their close signal (N7).
|
|
133
|
+
for (const socket of this.sockets)
|
|
134
|
+
if (!socket.writableEnded)
|
|
135
|
+
socket.destroy();
|
|
136
|
+
const server = this.server;
|
|
114
137
|
this.server = undefined;
|
|
115
|
-
|
|
138
|
+
if (server)
|
|
139
|
+
this.closing = new Promise(resolve => server.close(() => resolve()))
|
|
140
|
+
.then(() => rm(this.path, { force: true }).catch(() => { }));
|
|
116
141
|
}
|
|
117
|
-
|
|
118
|
-
|
|
142
|
+
isRegisteredRun(key, runId) {
|
|
143
|
+
return !this.stopped && typeof runId === 'string' && !!runId
|
|
144
|
+
&& (this.live.get(key) ?? []).some(entry => entry.runId === runId);
|
|
145
|
+
}
|
|
146
|
+
/** Register one run; release revokes only this exact registration, even when sessions overlap. */
|
|
147
|
+
register(run, tools, runId) {
|
|
119
148
|
this.runs.set(run.sessionKey, run);
|
|
120
|
-
const entry = tools ? { tools: new Set(tools) } : {};
|
|
149
|
+
const entry = { ...(tools ? { tools: new Set(tools) } : {}), runId };
|
|
121
150
|
const list = this.live.get(run.sessionKey);
|
|
122
151
|
if (list)
|
|
123
152
|
list.push(entry);
|
|
@@ -225,6 +254,24 @@ export class Bridge {
|
|
|
225
254
|
}
|
|
226
255
|
if (!isRecord(message) || typeof message.kind !== 'string')
|
|
227
256
|
return this.deny(socket, 'not a gate request');
|
|
257
|
+
if (message.kind === 'browser-capabilities') {
|
|
258
|
+
if (!this.browserCapabilities || this.stopped)
|
|
259
|
+
return this.deny(socket, 'browser unavailable');
|
|
260
|
+
try {
|
|
261
|
+
this.reply(socket, { capabilities: this.browserCapabilities() });
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
this.deny(socket, 'browser unavailable');
|
|
265
|
+
}
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (message.kind === 'before-agent-run') {
|
|
269
|
+
if (typeof message.key !== 'string' || !message.key || !this.beforeAgentRun)
|
|
270
|
+
return this.deny(socket, 'run unavailable');
|
|
271
|
+
void this.beforeAgentRun(message.key, typeof message.runId === 'string' ? message.runId : undefined)
|
|
272
|
+
.then(allow => this.reply(socket, { allow }), () => this.deny(socket, 'run unavailable'));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
228
275
|
if (message.kind === 'gate')
|
|
229
276
|
return void this.gate(socket, message);
|
|
230
277
|
if (message.kind === 'call')
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Member } from '../types.ts';
|
|
2
|
+
import type { LiveFrame, LiveInput } from '../browser.ts';
|
|
3
|
+
export type BrokerOptions = {
|
|
4
|
+
executablePath: string;
|
|
5
|
+
profileDir: string;
|
|
6
|
+
member: Member;
|
|
7
|
+
onExit(code: number | null): void;
|
|
8
|
+
};
|
|
9
|
+
export type LeaseBinding = {
|
|
10
|
+
epoch: number;
|
|
11
|
+
nonce: string;
|
|
12
|
+
origin: string;
|
|
13
|
+
knownIdps: string[];
|
|
14
|
+
};
|
|
15
|
+
export type PrivateState = {
|
|
16
|
+
origin: string;
|
|
17
|
+
secure: boolean;
|
|
18
|
+
offOrigin: boolean;
|
|
19
|
+
} | undefined;
|
|
20
|
+
export type Broker = {
|
|
21
|
+
bindLease(l: LeaseBinding | null): void;
|
|
22
|
+
confirmOrigin(l: {
|
|
23
|
+
epoch: number;
|
|
24
|
+
nonce: string;
|
|
25
|
+
}, origin: string): boolean;
|
|
26
|
+
privateState(): PrivateState;
|
|
27
|
+
clearSite(origins: string[]): Promise<void>;
|
|
28
|
+
endpoint(): {
|
|
29
|
+
cdpUrl: string;
|
|
30
|
+
};
|
|
31
|
+
fence(on: boolean): Promise<void>;
|
|
32
|
+
agentTab(): string | undefined;
|
|
33
|
+
originOf(targetId: string): Promise<string>;
|
|
34
|
+
openPrivate(url: string): Promise<string>;
|
|
35
|
+
closePrivate(): Promise<void>;
|
|
36
|
+
probe(url: string, verify: (p: Probe) => Promise<boolean>, timeoutMs: number): Promise<'ok' | 'fail' | 'timeout'>;
|
|
37
|
+
attachViewer(o: {
|
|
38
|
+
lease?: {
|
|
39
|
+
epoch: number;
|
|
40
|
+
nonce: string;
|
|
41
|
+
};
|
|
42
|
+
maxWidth?: number;
|
|
43
|
+
}): ViewerSession;
|
|
44
|
+
navigateAgent(url: string | 'reload'): Promise<void>;
|
|
45
|
+
close(): Promise<void>;
|
|
46
|
+
};
|
|
47
|
+
export type Probe = {
|
|
48
|
+
url: string;
|
|
49
|
+
status: number;
|
|
50
|
+
exists(selector: string): Promise<boolean>;
|
|
51
|
+
};
|
|
52
|
+
export type ViewerSession = {
|
|
53
|
+
frames: AsyncIterable<LiveFrame>;
|
|
54
|
+
states: AsyncIterable<NonNullable<PrivateState>>;
|
|
55
|
+
input(i: LiveInput): void;
|
|
56
|
+
close(): void;
|
|
57
|
+
};
|
|
58
|
+
/** Chromium refused to start without its sandbox (e.g. unprivileged user namespaces disabled). The broker never runs it unsandboxed. */
|
|
59
|
+
export declare class BrowserSandboxUnavailable extends Error {
|
|
60
|
+
readonly reason = "sandbox-unavailable";
|
|
61
|
+
constructor();
|
|
62
|
+
}
|
|
63
|
+
export declare function launchBroker(o: BrokerOptions): Promise<Broker>;
|