@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.
Files changed (59) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +174 -2
  3. package/dist/auth-store.js +1 -1
  4. package/dist/bridge.d.ts +10 -2
  5. package/dist/bridge.js +61 -14
  6. package/dist/browser/broker.d.ts +63 -0
  7. package/dist/browser/broker.js +990 -0
  8. package/dist/browser/host.d.ts +98 -0
  9. package/dist/browser/host.js +945 -0
  10. package/dist/browser/resume.d.ts +10 -0
  11. package/dist/browser/resume.js +23 -0
  12. package/dist/browser/store.d.ts +25 -0
  13. package/dist/browser/store.js +151 -0
  14. package/dist/browser/verify.d.ts +17 -0
  15. package/dist/browser/verify.js +38 -0
  16. package/dist/browser.d.ts +212 -0
  17. package/dist/browser.js +4 -0
  18. package/dist/config.d.ts +28 -0
  19. package/dist/config.js +130 -0
  20. package/dist/day-usage.d.ts +76 -0
  21. package/dist/day-usage.js +104 -0
  22. package/dist/device.d.ts +32 -2
  23. package/dist/device.js +224 -4
  24. package/dist/engine-patches.d.ts +36 -0
  25. package/dist/engine-patches.js +320 -0
  26. package/dist/engine-status.d.ts +13 -0
  27. package/dist/engine-status.js +31 -0
  28. package/dist/engine.d.ts +13 -1
  29. package/dist/engine.js +99 -52
  30. package/dist/index.d.ts +6 -1
  31. package/dist/index.js +2 -0
  32. package/dist/kit.d.ts +29 -3
  33. package/dist/kit.js +324 -13
  34. package/dist/link.d.ts +5 -2
  35. package/dist/link.js +333 -4
  36. package/dist/routes.d.ts +19 -8
  37. package/dist/routes.js +44 -11
  38. package/dist/routes.json +3775 -356
  39. package/dist/runs.js +8 -2
  40. package/dist/signin.d.ts +2 -1
  41. package/dist/signin.js +103 -7
  42. package/dist/testing/browser.d.ts +50 -0
  43. package/dist/testing/browser.js +166 -0
  44. package/dist/testing/index.d.ts +1 -0
  45. package/dist/testing/index.js +1 -0
  46. package/dist/testing/model-stub.d.ts +1 -0
  47. package/dist/testing/model-stub.js +3 -1
  48. package/dist/types.d.ts +50 -3
  49. package/dist/usage-boots.d.ts +8 -0
  50. package/dist/usage-boots.js +26 -0
  51. package/dist/usage.d.ts +51 -0
  52. package/dist/usage.js +61 -0
  53. package/dist/words.json +42 -0
  54. package/engine/OPENCLAW-LICENSE +24 -0
  55. package/engine/patches.json +59 -0
  56. package/engine/patches.schema.json +41 -0
  57. package/package.json +12 -4
  58. package/plugin/index.js +78 -5
  59. package/plugin/usage.js +125 -0
@@ -0,0 +1,10 @@
1
+ import type { NeedSignIn, ResumeState } from '../browser.ts';
2
+ export type ResumeDispatch = (input: {
3
+ member: string;
4
+ sessionKey: string;
5
+ idempotencyKey: string;
6
+ message: string;
7
+ }) => Promise<'accepted' | 'refused' | 'unknown'>;
8
+ export declare function newResume(request: NeedSignIn, attempt?: number): ResumeState;
9
+ export declare function recoverResume(request: NeedSignIn): void;
10
+ export declare function dispatchResume(request: NeedSignIn, dispatch: ResumeDispatch): Promise<ResumeState['state']>;
@@ -0,0 +1,23 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export function newResume(request, attempt = 1) {
3
+ return { key: `signin:${request.id}:resume:${attempt}:${randomUUID()}`, attempt, state: 'pending' };
4
+ }
5
+ export function recoverResume(request) {
6
+ const resume = request.settled?.resume;
7
+ // Neither process liveness nor an evictable gateway cache is submission proof.
8
+ if (resume && ['pending', 'accepted', 'submitted'].includes(resume.state))
9
+ resume.state = 'indeterminate';
10
+ }
11
+ export async function dispatchResume(request, dispatch) {
12
+ const resume = request.settled?.resume;
13
+ if (request.settled?.state !== 'verified' || resume?.state !== 'pending')
14
+ throw new Error('resume unavailable');
15
+ try {
16
+ const answer = await dispatch({ member: request.member, sessionKey: request.sessionKey, idempotencyKey: resume.key,
17
+ message: `The person signed in to ${request.site}. Continue the task.` });
18
+ return answer === 'accepted' ? 'accepted' : answer === 'refused' ? 'failed' : 'indeterminate';
19
+ }
20
+ catch {
21
+ return 'indeterminate';
22
+ }
23
+ }
@@ -0,0 +1,25 @@
1
+ import type { NeedSignIn } from '../browser.ts';
2
+ import type { Member } from '../types.ts';
3
+ export type SignInData = {
4
+ v: 1;
5
+ requests: NeedSignIn[];
6
+ host: Record<string, {
7
+ checkUrl: string;
8
+ confirmed: string[];
9
+ }>;
10
+ verified: Record<Member, string[]>;
11
+ };
12
+ export interface SignInStore {
13
+ read(): SignInData;
14
+ write(data: SignInData): void;
15
+ }
16
+ export declare const emptySignIns: () => SignInData;
17
+ export declare class BrowserStoreError extends Error {
18
+ constructor();
19
+ }
20
+ export declare function validOrigin(value: string): string;
21
+ export declare function checkUrl(value: string): string;
22
+ export declare function validateSignIns(value: unknown): SignInData;
23
+ export declare function fileSignInStore(stateDir: string, io?: {
24
+ sync?: (fd: number) => void;
25
+ }): SignInStore;
@@ -0,0 +1,151 @@
1
+ // Host-only durable state. One kit owns stateDir; host serializes every mutation across members.
2
+ import { chmodSync, closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { randomUUID } from 'node:crypto';
5
+ export const emptySignIns = () => ({ v: 1, requests: [], host: {}, verified: {} });
6
+ export class BrowserStoreError extends Error {
7
+ constructor() { super('browser state unavailable'); }
8
+ }
9
+ const strings = (v) => Array.isArray(v) && v.every(x => typeof x === 'string');
10
+ const object = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
11
+ const integer = (v) => Number.isSafeInteger(v) && Number(v) >= 0;
12
+ export function validOrigin(value) {
13
+ try {
14
+ const u = new URL(value);
15
+ if (!['http:', 'https:'].includes(u.protocol) || u.username || u.password || value !== u.origin)
16
+ throw 0;
17
+ return u.origin;
18
+ }
19
+ catch {
20
+ throw new BrowserStoreError();
21
+ }
22
+ }
23
+ export function checkUrl(value) {
24
+ try {
25
+ const u = new URL(value);
26
+ if (!['http:', 'https:'].includes(u.protocol) || u.username || u.password)
27
+ throw 0;
28
+ // No credential-bearing fragments or URL parameters in durable host state.
29
+ if (u.hash || [...u.searchParams.keys()].some(k => /password|passwd|secret|token|code|authorization/i.test(k)))
30
+ throw 0;
31
+ return u.href;
32
+ }
33
+ catch {
34
+ throw new BrowserStoreError();
35
+ }
36
+ }
37
+ export function validateSignIns(value) {
38
+ try {
39
+ if (!object(value) || value.v !== 1 || !Array.isArray(value.requests) || !object(value.host) || !object(value.verified)
40
+ || Object.keys(value).some(k => !['v', 'requests', 'host', 'verified'].includes(k)))
41
+ throw 0;
42
+ const ids = new Set();
43
+ const active = new Set();
44
+ for (const r of value.requests) {
45
+ if (!object(r) || typeof r.id !== 'string' || !/^[A-Za-z0-9_-]{22}$/.test(r.id) || ids.has(r.id)
46
+ || typeof r.member !== 'string' || !/^[a-z][a-z0-9-]{0,31}$/.test(r.member) || !integer(r.gen) || r.gen < 1
47
+ || typeof r.sessionKey !== 'string' || !r.sessionKey || typeof r.origin !== 'string' || typeof r.site !== 'string'
48
+ || typeof r.secure !== 'boolean' || typeof r.firstTime !== 'boolean' || !integer(r.at) || !integer(r.expires)
49
+ || !strings(r.reasons) || !strings(r.hints) || !Array.isArray(r.choices)
50
+ || !['waiting', 'held', 'checking', 'parked', 'settled'].includes(String(r.state))
51
+ || Object.keys(r).some(k => !['id', 'gen', 'prev', 'member', 'sessionKey', 'origin', 'site', 'secure', 'firstTime', 'reasons', 'agentNote', 'hints', 'choices', 'state', 'settled', 'at', 'expires'].includes(k))
52
+ || r.reasons.some(x => !['redirected-to-login', 'password-field', 'http-401', 'agent-asked', 'session-expired'].includes(x))
53
+ || r.hints.some(x => !['password', 'google', 'microsoft', 'apple', 'sso', 'passkey', 'code', 'other'].includes(x))
54
+ || r.choices.some(x => !object(x) || Object.keys(x).length !== 1 || !['takeover', 'not-now', 'cancel'].includes(String(x.kind))))
55
+ throw 0;
56
+ validOrigin(r.origin);
57
+ if (r.state === 'settled') {
58
+ if (!object(r.settled) || !['verified', 'entered-unverified', 'cancelled', 'expired', 'failed'].includes(String(r.settled.state))
59
+ || !integer(r.settled.at) || Object.keys(r.settled).some(k => !['state', 'reason', 'at', 'resume'].includes(k)))
60
+ throw 0;
61
+ const resume = r.settled.resume;
62
+ if (resume !== undefined && (!object(resume) || r.settled.state !== 'verified' || !integer(resume.attempt) || resume.attempt < 1
63
+ || typeof resume.key !== 'string' || !resume.key.startsWith(`signin:${r.id}:resume:${resume.attempt}:`)
64
+ || !['pending', 'accepted', 'submitted', 'indeterminate', 'failed'].includes(String(resume.state))
65
+ || Object.keys(resume).some(k => !['key', 'attempt', 'state'].includes(k))))
66
+ throw 0;
67
+ }
68
+ else {
69
+ if (r.settled !== undefined || active.has(r.member))
70
+ throw 0;
71
+ active.add(r.member);
72
+ }
73
+ ids.add(r.id);
74
+ const h = value.host[r.id];
75
+ if (!object(h) || typeof h.checkUrl !== 'string' || !strings(h.confirmed) || Object.keys(h).some(k => !['checkUrl', 'confirmed'].includes(k)))
76
+ throw 0;
77
+ checkUrl(h.checkUrl);
78
+ h.confirmed.forEach(validOrigin);
79
+ }
80
+ if (Object.keys(value.host).some(id => !ids.has(id)))
81
+ throw 0;
82
+ for (const [member, sites] of Object.entries(value.verified)) {
83
+ if (!/^[a-z][a-z0-9-]{0,31}$/.test(member) || !strings(sites))
84
+ throw 0;
85
+ }
86
+ return structuredClone(value);
87
+ }
88
+ catch {
89
+ throw new BrowserStoreError();
90
+ }
91
+ }
92
+ export function fileSignInStore(stateDir, io = {}) {
93
+ const dir = join(stateDir, 'browser');
94
+ const file = join(dir, 'signins.json');
95
+ let failed = false;
96
+ const sync = io.sync ?? fsyncSync;
97
+ const guard = () => { if (failed)
98
+ throw new BrowserStoreError(); };
99
+ return {
100
+ read() {
101
+ guard();
102
+ try {
103
+ if (!existsSync(file))
104
+ return emptySignIns();
105
+ if (!lstatSync(dir).isDirectory() || lstatSync(dir).isSymbolicLink() || !lstatSync(file).isFile() || lstatSync(file).isSymbolicLink())
106
+ throw 0;
107
+ chmodSync(file, 0o600);
108
+ return validateSignIns(JSON.parse(readFileSync(file, 'utf8')));
109
+ }
110
+ catch {
111
+ failed = true;
112
+ throw new BrowserStoreError();
113
+ }
114
+ },
115
+ write(data) {
116
+ guard();
117
+ const tmp = join(dir, `.signins-${randomUUID()}`);
118
+ try {
119
+ const safe = validateSignIns(data);
120
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
121
+ if (!lstatSync(dir).isDirectory() || lstatSync(dir).isSymbolicLink())
122
+ throw 0;
123
+ chmodSync(dir, 0o700);
124
+ writeFileSync(tmp, JSON.stringify(safe), { flag: 'wx', mode: 0o600 });
125
+ const fd = openSync(tmp, 'r');
126
+ try {
127
+ sync(fd);
128
+ }
129
+ finally {
130
+ closeSync(fd);
131
+ }
132
+ renameSync(tmp, file);
133
+ // A failed directory fsync is ambiguous: poison this store, never dispatch or unfence.
134
+ const directory = openSync(dir, 'r');
135
+ try {
136
+ sync(directory);
137
+ }
138
+ finally {
139
+ closeSync(directory);
140
+ }
141
+ }
142
+ catch {
143
+ failed = true;
144
+ throw new BrowserStoreError();
145
+ }
146
+ finally {
147
+ rmSync(tmp, { force: true });
148
+ }
149
+ },
150
+ };
151
+ }
@@ -0,0 +1,17 @@
1
+ import type { SiteVerifier, SettledReason } from '../browser.ts';
2
+ export type Probe = {
3
+ url: string;
4
+ status: number;
5
+ exists(selector: string): Promise<boolean>;
6
+ };
7
+ export type Prober = {
8
+ probe(url: string, verify: (p: Probe) => Promise<boolean>, timeoutMs: number): Promise<'ok' | 'fail' | 'timeout'>;
9
+ };
10
+ export type Verification = {
11
+ state: 'verified';
12
+ } | {
13
+ state: 'entered-unverified';
14
+ reason: SettledReason;
15
+ };
16
+ export declare function validateVerifiers(verifiers: SiteVerifier[]): void;
17
+ export declare function verifySignIn(broker: Prober, origin: string, verifiers: SiteVerifier[], timeoutMs: number): Promise<Verification>;
@@ -0,0 +1,38 @@
1
+ import { checkUrl, validOrigin } from "./store.js";
2
+ export function validateVerifiers(verifiers) {
3
+ for (const v of verifiers) {
4
+ validOrigin(v.origin);
5
+ if (new URL(checkUrl(v.url)).origin !== v.origin || (v.status !== undefined && (!Number.isInteger(v.status) || v.status < 100 || v.status > 599))) {
6
+ throw new Error('invalid browser verifier');
7
+ }
8
+ }
9
+ }
10
+ export async function verifySignIn(broker, origin, verifiers, timeoutMs) {
11
+ const v = verifiers.find(v => v.origin === origin);
12
+ // A bare endpoint status is not positive proof; host code or a signed-in-only selector is required.
13
+ if (!v || (!v.selector && !v.check))
14
+ return { state: 'entered-unverified', reason: 'no-verifier' };
15
+ let timer;
16
+ try {
17
+ const result = await Promise.race([
18
+ broker.probe(v.url, async (p) => {
19
+ if (new URL(p.url).origin !== origin || (v.status !== undefined && p.status !== v.status))
20
+ return false;
21
+ if (v.selector && !await p.exists(v.selector))
22
+ return false;
23
+ if (v.check && !await v.check(p))
24
+ return false;
25
+ // The verifier may have navigated: check the final host-side URL again.
26
+ return new URL(p.url).origin === origin;
27
+ }, timeoutMs),
28
+ new Promise(resolve => { timer = setTimeout(() => resolve('timeout'), timeoutMs); }),
29
+ ]);
30
+ return result === 'ok' ? { state: 'verified' } : { state: 'entered-unverified', reason: result === 'timeout' ? 'check-timeout' : 'still-signed-out' };
31
+ }
32
+ catch {
33
+ return { state: 'entered-unverified', reason: 'still-signed-out' };
34
+ }
35
+ finally {
36
+ clearTimeout(timer);
37
+ }
38
+ }
@@ -0,0 +1,212 @@
1
+ import type { Member } from './types.ts';
2
+ /** What a live view shows. A browser source never claims to be a whole computer. */
3
+ export type LiveSource = {
4
+ kind: 'browser';
5
+ member: Member;
6
+ } | {
7
+ kind: 'desktop';
8
+ member: Member;
9
+ source: 'host' | 'node' | 'environment';
10
+ };
11
+ /** One member's kit-owned browser: one Chromium process + one profile per member, inside one kit (one person). */
12
+ export type BrowserState = {
13
+ member: Member;
14
+ phase: 'off' | 'starting' | 'ready' | 'recovering' | 'fenced' | 'blocked';
15
+ why?: 'no-browser' | 'recovery-exhausted' | 'engine-detached' | 'unsafe-tools' | 'gate-off' | 'handoff-unprotected';
16
+ recovery?: {
17
+ attempt: number;
18
+ of: number;
19
+ nextAt?: number;
20
+ };
21
+ };
22
+ /** Display hints read from the page by the host (button text, input types). Never from the model. */
23
+ export type SignInMethodHint = 'password' | 'google' | 'microsoft' | 'apple' | 'sso' | 'passkey' | 'code' | 'other';
24
+ export type SignInReason = 'redirected-to-login' | 'password-field' | 'http-401' | 'agent-asked' | 'session-expired';
25
+ export type SignInChoice = {
26
+ kind: 'takeover';
27
+ } | {
28
+ kind: 'not-now';
29
+ } | {
30
+ kind: 'cancel';
31
+ };
32
+ export type SettledState = 'verified' | 'entered-unverified' | 'cancelled' | 'expired' | 'failed';
33
+ export type SettledReason = 'still-signed-out' | 'origin-mismatch' | 'browser-gone' | 'superseded' | 'check-timeout' | 'run-replaced' | 'no-verifier';
34
+ /** Resume bookkeeping for a verified request. Persisted BEFORE dispatch; never claims exactly-once. */
35
+ export type ResumeState = {
36
+ key: string;
37
+ attempt: number;
38
+ state: 'pending' | 'accepted' | 'submitted' | 'indeterminate' | 'failed';
39
+ };
40
+ export type NeedSignIn = {
41
+ id: string;
42
+ gen: number;
43
+ prev?: string;
44
+ member: Member;
45
+ sessionKey: string;
46
+ origin: string;
47
+ site: string;
48
+ secure: boolean;
49
+ firstTime: boolean;
50
+ reasons: SignInReason[];
51
+ agentNote?: string;
52
+ hints: SignInMethodHint[];
53
+ choices: SignInChoice[];
54
+ state: 'waiting' | 'held' | 'checking' | 'parked' | 'settled';
55
+ settled?: {
56
+ state: SettledState;
57
+ reason?: SettledReason;
58
+ at: number;
59
+ resume?: ResumeState;
60
+ };
61
+ at: number;
62
+ expires: number;
63
+ };
64
+ /** The single controlling viewer's lease. Bound to the grant that took it. */
65
+ export type TakeoverLease = {
66
+ requestId: string;
67
+ gen: number;
68
+ epoch: number;
69
+ nonce: string;
70
+ expires: number;
71
+ claimMs: number;
72
+ graceMs: number;
73
+ };
74
+ export type LiveViewState = {
75
+ source: LiveSource;
76
+ phase: 'connecting' | 'live' | 'reconnecting' | 'private' | 'ended' | 'failed';
77
+ mode: 'observe' | 'control';
78
+ origin?: string;
79
+ secure?: boolean;
80
+ offOrigin?: boolean;
81
+ leaseExpires?: number;
82
+ why?: 'revoked' | 'expired' | 'stale-epoch' | 'superseded' | 'browser-gone' | 'unsupported';
83
+ };
84
+ /** JPEG frame on the link stream. Never written to disk, logs, transcripts, relay jobs or telemetry. */
85
+ export type LiveFrame = {
86
+ seq: number;
87
+ w: number;
88
+ h: number;
89
+ at: number;
90
+ jpeg: Uint8Array;
91
+ };
92
+ /** Lease holder only, private tab only. Never logged; never in errors or events. */
93
+ export type LiveInput = {
94
+ kind: 'pointer';
95
+ type: 'move' | 'down' | 'up' | 'wheel';
96
+ x: number;
97
+ y: number;
98
+ button?: 'left' | 'right' | 'middle';
99
+ dx?: number;
100
+ dy?: number;
101
+ } | {
102
+ kind: 'key';
103
+ type: 'down' | 'up';
104
+ key: string;
105
+ code?: string;
106
+ modifiers?: number;
107
+ } | {
108
+ kind: 'text';
109
+ text: string;
110
+ } | {
111
+ kind: 'nav';
112
+ action: 'back' | 'forward' | 'reload';
113
+ };
114
+ export type ThumbnailResult = {
115
+ state: 'ok';
116
+ frame: LiveFrame;
117
+ } | {
118
+ state: 'private' | 'off' | 'unsupported';
119
+ };
120
+ export type SignInRefusedWhy = 'stale' | 'not-found' | 'held-by-other' | 'lease-expired' | 'not-control' | 'not-lease-holder' | 'confirm-site' | 'already-open' | 'insecure-remote' | 'unsupported';
121
+ export type BrowserOptions = {
122
+ executablePath: string;
123
+ members: Member[] | 'all';
124
+ requestTtlMs?: number;
125
+ leaseTtlMs?: number;
126
+ claimMs?: number;
127
+ graceMs?: number;
128
+ checkMs?: number;
129
+ recovery?: {
130
+ attempts: number;
131
+ backoffMs: number[];
132
+ };
133
+ knownIdps?: string[];
134
+ verifiers?: SiteVerifier[];
135
+ };
136
+ /** Host-only positive proof that the browser session is signed in. Run by the host in a host-only tab after Done;
137
+ * the result is a boolean plus a reason code. No page text, cookie, header or value leaves it; never evented,
138
+ * logged or shown to a model. A login-free page or a clean 200 alone never counts. */
139
+ export type SiteVerifier = {
140
+ origin: string;
141
+ url: string;
142
+ status?: number;
143
+ selector?: string;
144
+ check?: (probe: {
145
+ url: string;
146
+ status: number;
147
+ exists(selector: string): Promise<boolean>;
148
+ }) => Promise<boolean>;
149
+ };
150
+ export interface BrowserHost {
151
+ state(member: Member): BrowserState;
152
+ signIns(member?: Member): NeedSignIn[];
153
+ takeover(id: string, gen: number, by: {
154
+ grant: string;
155
+ confirmSite?: string;
156
+ }): Promise<TakeoverLease>;
157
+ confirmOrigin(lease: Pick<TakeoverLease, 'requestId' | 'epoch' | 'nonce'>, origin: string): void;
158
+ done(lease: Pick<TakeoverLease, 'requestId' | 'epoch' | 'nonce'>): Promise<NeedSignIn>;
159
+ notNow(id: string, gen: number, by: {
160
+ grant: string;
161
+ }): Promise<NeedSignIn>;
162
+ reopen(id: string, gen: number, by: {
163
+ grant: string;
164
+ }): Promise<NeedSignIn>;
165
+ retry(id: string, gen: number, by: {
166
+ grant: string;
167
+ }): Promise<NeedSignIn>;
168
+ cancel(id: string, gen: number, by: {
169
+ grant: string;
170
+ }): Promise<NeedSignIn>;
171
+ thumbnail(source: LiveSource, by: {
172
+ grant: string;
173
+ }): Promise<ThumbnailResult>;
174
+ live(source: LiveSource, o: {
175
+ grant: string;
176
+ lease?: TakeoverLease;
177
+ maxWidth?: number;
178
+ }, on: {
179
+ state(s: LiveViewState): void;
180
+ frame(f: LiveFrame): void;
181
+ }): {
182
+ input(i: LiveInput): void;
183
+ close(): void;
184
+ };
185
+ forget(member: Member, site: string | 'all', by: {
186
+ grant: string;
187
+ }): Promise<void>;
188
+ }
189
+ export type BrowserDevice = {
190
+ state(member: Member): Promise<BrowserState>;
191
+ signIns(): Promise<NeedSignIn[]>;
192
+ takeover(id: string, gen: number, o?: {
193
+ confirmSite?: string;
194
+ }): Promise<TakeoverLease>;
195
+ confirmOrigin(lease: TakeoverLease, origin: string): Promise<void>;
196
+ done(lease: TakeoverLease): Promise<NeedSignIn>;
197
+ notNow(id: string, gen: number): Promise<NeedSignIn>;
198
+ reopen(id: string, gen: number): Promise<NeedSignIn>;
199
+ retry(id: string, gen: number): Promise<NeedSignIn>;
200
+ cancel(id: string, gen: number): Promise<NeedSignIn>;
201
+ forget(member: Member, site: string | 'all'): Promise<void>;
202
+ thumbnail(source: LiveSource): Promise<ThumbnailResult>;
203
+ live(source: LiveSource, o?: {
204
+ lease?: TakeoverLease;
205
+ maxWidth?: number;
206
+ }): {
207
+ states: AsyncIterable<LiveViewState>;
208
+ frames: AsyncIterable<LiveFrame>;
209
+ input(i: LiveInput): void;
210
+ close(): void;
211
+ };
212
+ };
@@ -0,0 +1,4 @@
1
+ // Browser sign-in handoff and live view: the portable typed interface (docs/runtime-kits.md 5.17, D20).
2
+ // Types only, no runtime: the broker, host, kit wiring, link ops and device client are built by O18-O25 against these
3
+ // shapes. Handoff stays refused (BrowserState why 'handoff-unprotected') until parked sessions are protected (5.17).
4
+ export {};
package/dist/config.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { KitOptions } from './kit.ts';
2
2
  import type { Member } from './types.ts';
3
+ import type { NeedSignIn } from './browser.ts';
4
+ type Obj = Record<string, any>;
3
5
  export declare function reconcileConfig(saved: object | undefined, o: {
4
6
  root: string;
5
7
  stateDir: string;
@@ -9,5 +11,31 @@ export declare function reconcileConfig(saved: object | undefined, o: {
9
11
  policyPath: string;
10
12
  app?: object;
11
13
  installPolicy?: KitOptions['installPolicy'];
14
+ browser?: {
15
+ profiles: Record<string, {
16
+ cdpUrl: string;
17
+ attachOnly: true;
18
+ }>;
19
+ tools: string[];
20
+ };
12
21
  }): object;
22
+ /** Caller-owned task namespaces, including the engine key-agent rewrite; never an entire member. */
23
+ export declare function appRecoveryPrefixes(ownership?: KitOptions['appOwnedSessions']): string[];
24
+ /** A closed explicit global allow is required, not a group deny or an OS sandbox claim. Every nested
25
+ * agent, delegate and provider policy must stay inside it. The runtime also checks tools.effective. */
26
+ export declare function browserToolPolicySafe(config: unknown, tools: readonly string[]): boolean;
27
+ export declare function browserProfileAcknowledged(profile: unknown, owned: unknown, endpoint: string, snapshot: {
28
+ configRevisionHash?: unknown;
29
+ appliedConfigHash?: unknown;
30
+ }, stableFile: boolean): boolean;
31
+ export declare function browserSessionMayRun(requests: readonly NeedSignIn[], key: string, registeredRun?: boolean): boolean;
13
32
  export declare function memoryLimited(config: object, member: Member): boolean;
33
+ /** One dotted path, validated at the boundary: a caller-supplied walk must never reach a prototype. */
34
+ export declare function configKeyPath(key: string): string[];
35
+ /** The value at one dotted path, or undefined when it is absent. A copy, so a caller cannot hold kit state. */
36
+ export declare function readConfigKey(config: unknown, segments: readonly string[]): unknown;
37
+ /** Writes one dotted path, creating only the missing objects on the way, and returns the value it replaced.
38
+ * undefined removes the key, and with it the empty objects this call created, so a removal restores the file it
39
+ * read. Every other key keeps its place, so one narrow set stays one narrow set. */
40
+ export declare function writeConfigKey(config: Obj, segments: readonly string[], value: unknown): unknown;
41
+ export {};