@xingular/harness 1.5.0-unstable → 1.6.0-unstable

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 (95) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/browser-extension/README.md +32 -0
  3. package/browser-extension/background.js +612 -0
  4. package/browser-extension/manifest.json +16 -0
  5. package/browser-extension/overlay.js +97 -0
  6. package/browser-extension/popup.css +19 -0
  7. package/browser-extension/popup.html +30 -0
  8. package/browser-extension/popup.js +101 -0
  9. package/browser-extension/site-access.js +63 -0
  10. package/dist/cli.js +1489 -680
  11. package/dist/sdk/browser/config.d.ts +3 -0
  12. package/dist/sdk/browser/config.js +2 -1
  13. package/dist/sdk/browser/extension/bridge.d.ts +55 -0
  14. package/dist/sdk/browser/extension/bridge.js +336 -0
  15. package/dist/sdk/browser/extension/session.d.ts +50 -0
  16. package/dist/sdk/browser/extension/session.js +321 -0
  17. package/dist/sdk/browser/index.d.ts +7 -2
  18. package/dist/sdk/browser/index.js +58 -25
  19. package/dist/sdk/browser/serverMethods.js +4 -1
  20. package/dist/sdk/browser/tools.d.ts +99 -2
  21. package/dist/sdk/browser/tools.js +6 -1
  22. package/dist/sdk/commands/registry.d.ts +5 -0
  23. package/dist/sdk/commands/registry.js +1 -0
  24. package/dist/sdk/harness.d.ts +4 -0
  25. package/dist/sdk/harness.js +1 -1
  26. package/dist/sdk/pi/retryPolicy.d.ts +19 -0
  27. package/dist/sdk/pi/retryPolicy.js +11 -0
  28. package/dist/sdk/prompt/compile.d.ts +0 -2
  29. package/dist/sdk/prompt/compile.js +2 -6
  30. package/dist/sdk/prompt/reporting.d.ts +1 -1
  31. package/dist/sdk/prompt/reporting.js +30 -13
  32. package/dist/sdk/prompt/standard.d.ts +1 -1
  33. package/dist/sdk/prompt/standard.js +1 -1
  34. package/dist/sdk/remote/webClient.js +6 -2
  35. package/dist/sdk/sessions/contracts.d.ts +2 -0
  36. package/dist/sdk/sessions/contracts.js +5 -1
  37. package/dist/sdk/sessions/planMode.d.ts +16 -6
  38. package/dist/sdk/sessions/planMode.js +59 -23
  39. package/dist/sdk/sessions/runner.js +14 -5
  40. package/dist/sdk/sessions/workSlice.d.ts +15 -1
  41. package/dist/sdk/sessions/workSlice.js +17 -9
  42. package/dist/sdk/terminal/controller.d.ts +6 -0
  43. package/dist/sdk/terminal/controller.js +44 -5
  44. package/dist/sdk/terminal/opentui/model.js +3 -1
  45. package/dist/sdk/terminal/piExtension.js +7 -2
  46. package/dist/sdk/terminal/stallWatch.d.ts +18 -0
  47. package/dist/sdk/terminal/stallWatch.js +20 -0
  48. package/dist/sdk/terminal/subagentMonitor.d.ts +2 -0
  49. package/dist/sdk/terminal/subagentMonitor.js +4 -1
  50. package/dist/sdk/terminal/terminals.js +35 -4
  51. package/dist/sdk/terminal/toolName.d.ts +6 -0
  52. package/dist/sdk/terminal/toolName.js +12 -0
  53. package/dist/sdk/terminal/tui/commandPages.d.ts +1 -1
  54. package/dist/sdk/terminal/tui/commandPages.js +3 -0
  55. package/dist/sdk/terminal/tui/commands.d.ts +14 -0
  56. package/dist/sdk/terminal/tui/commands.js +4 -1
  57. package/dist/sdk/terminal/tui/pages/followUpQueuePage.d.ts +34 -0
  58. package/dist/sdk/terminal/tui/pages/followUpQueuePage.js +130 -0
  59. package/dist/sdk/terminal/tui/pages/trajectoryPage.js +37 -7
  60. package/dist/sdk/terminal/tui/runtime.js +2 -1
  61. package/dist/sdk/terminal/tui/settingsModel.js +88 -7
  62. package/dist/sdk/tools/askUser.js +2 -2
  63. package/dist/sdk/world/world.d.ts +12 -2
  64. package/dist/sdk/world/world.js +42 -4
  65. package/dist/terminal-opentui.js +1456 -756
  66. package/dist/terminal-pi.js +62 -31
  67. package/docs/README.md +1 -0
  68. package/docs/architecture/02-sessions-and-runs.md +43 -25
  69. package/docs/architecture/04-prompt.md +23 -22
  70. package/docs/architecture/10-server-protocol.md +23 -3
  71. package/docs/architecture/11-terminal.md +41 -29
  72. package/docs/architecture/12-storage.md +2 -1
  73. package/docs/architecture/13-world-tools.md +35 -22
  74. package/docs/architecture/14-surfaces-and-parity.md +24 -20
  75. package/docs/architecture/README.md +4 -1
  76. package/docs/architecture/proposals/PROP-0026-connected-sketch-display.md +3 -1
  77. package/docs/decisions/ADR-0027-connected-sketch-display.md +4 -2
  78. package/docs/decisions/ADR-0076-codex-final-answer-verbatim.md +6 -1
  79. package/docs/decisions/ADR-0077-plan-runs-may-conclude-no-plan-needed.md +120 -0
  80. package/docs/decisions/ADR-0078-child-aware-stall-watch.md +46 -0
  81. package/docs/decisions/ADR-0079-phase-led-reporting-guidance.md +72 -0
  82. package/docs/decisions/ADR-0080-fixed-width-question-sketch-canvas.md +50 -0
  83. package/docs/decisions/ADR-0081-no-work-slice-budget-by-default.md +36 -0
  84. package/docs/decisions/ADR-0082-connected-brave-browser.md +72 -0
  85. package/docs/parity.md +7 -5
  86. package/docs/releases/1.5.0-unstable.md +12 -3
  87. package/docs/releases/1.5.1-unstable.md +24 -0
  88. package/docs/releases/1.6.0-unstable.md +59 -0
  89. package/docs/study/00-index.md +17 -1
  90. package/docs/study/STUDY-0007-claude-code-prompt.md +162 -0
  91. package/docs/study/STUDY-0008-browser-control.md +281 -0
  92. package/docs/study/STUDY-0009-claude-code-reporting.md +205 -0
  93. package/docs/study/sources/claude-code.md +51 -39
  94. package/docs/terminal-tui.md +46 -18
  95. package/package.json +2 -1
@@ -1,4 +1,7 @@
1
+ export type BrowserBackendSelection = 'isolated' | 'brave';
1
2
  export interface BrowserConfig {
3
+ /** The isolated harness profile or the connected Brave profile. */
4
+ backend: BrowserBackendSelection;
2
5
  /** Show the browser window instead of running it headless. */
3
6
  headed: boolean;
4
7
  /** Attach to a browser a person already runs (its DevTools address) instead of launching one. */
@@ -3,11 +3,12 @@
3
3
  // another endpoint or profile.
4
4
  import { join } from 'node:path';
5
5
  import { readJsonOr, writeJson } from "../core/fs.js";
6
- export const DEFAULT_BROWSER_CONFIG = { headed: false, cdpUrl: null, executablePath: null, allowPrivate: false, allowedDomains: [] };
6
+ export const DEFAULT_BROWSER_CONFIG = { backend: 'isolated', headed: false, cdpUrl: null, executablePath: null, allowPrivate: false, allowedDomains: [] };
7
7
  export const configPath = (stateDir) => join(stateDir, 'browser.json');
8
8
  export function readBrowserConfig(stateDir) {
9
9
  const saved = readJsonOr(configPath(stateDir), {});
10
10
  return {
11
+ backend: saved.backend === 'brave' ? 'brave' : 'isolated',
11
12
  headed: saved.headed === true,
12
13
  cdpUrl: typeof saved.cdpUrl === 'string' && /^(?:https?|wss?):\/\/(?:127\.0\.0\.1|localhost|\[::1\])(?::\d+)?(?:\/|$)/.test(saved.cdpUrl) ? saved.cdpUrl : null,
13
14
  executablePath: typeof saved.executablePath === 'string' && saved.executablePath.trim() ? saved.executablePath : null,
@@ -0,0 +1,55 @@
1
+ export interface BrowserExtensionStatus {
2
+ state: 'stopped' | 'pairing' | 'waiting' | 'connected' | 'paused';
3
+ paired: boolean;
4
+ connected: boolean;
5
+ paused: boolean;
6
+ port: number | null;
7
+ lastSeenAt: number | null;
8
+ }
9
+ export interface BrowserExtensionStart {
10
+ status: BrowserExtensionStatus;
11
+ pairingCode?: string;
12
+ expiresAt?: number;
13
+ }
14
+ export declare class BrowserExtensionBridge {
15
+ private readonly options;
16
+ private server;
17
+ private starting;
18
+ private port;
19
+ private accessToken;
20
+ private origin;
21
+ private pairing;
22
+ private lastSeenAt;
23
+ private paused;
24
+ private waiter;
25
+ private waiterTimer;
26
+ private readonly queue;
27
+ private readonly sent;
28
+ private attempts;
29
+ private nextAttemptAt;
30
+ constructor(options: {
31
+ stateDir: string;
32
+ port?: number;
33
+ });
34
+ private settingsFile;
35
+ private save;
36
+ status(): BrowserExtensionStatus;
37
+ /** Starts the loopback bridge and returns a short-lived code for the extension's explicit pair action. */
38
+ start(): Promise<BrowserExtensionStart>;
39
+ private startResult;
40
+ private listen;
41
+ /** One request from the browser at a time; the extension's poll loop serializes the live tabs. */
42
+ request(operation: unknown): Promise<unknown>;
43
+ stop(): Promise<BrowserExtensionStatus>;
44
+ /** Keeps pairing across a normal Harness shutdown so the extension can reconnect to this profile. */
45
+ close(): Promise<void>;
46
+ private revoke;
47
+ private rejectPending;
48
+ private closeServer;
49
+ private flush;
50
+ private json;
51
+ private body;
52
+ private originAllowed;
53
+ private authorized;
54
+ private handle;
55
+ }
@@ -0,0 +1,336 @@
1
+ import { randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
2
+ import { chmodSync, existsSync, mkdirSync, rmSync } from 'node:fs';
3
+ import { createServer } from 'node:http';
4
+ import { join } from 'node:path';
5
+ import { readJsonOr, writeJson } from "../../core/fs.js";
6
+ const DEFAULT_PORT = 38671;
7
+ const GONE_MS = 45_000;
8
+ const POLL_MS = 20_000;
9
+ const REQUEST_MS = 60_000;
10
+ const PAIR_MS = 5 * 60_000;
11
+ const MAX_BODY = 8_000_000;
12
+ const EXTENSION_ORIGIN = /^chrome-extension:\/\/([a-p]{32})$/;
13
+ const bounded = (value, max) => typeof value === 'string' ? Array.from(value).slice(0, max).join('') : '';
14
+ function sameSecret(candidate, expected) {
15
+ const left = Buffer.from(candidate);
16
+ const right = Buffer.from(expected);
17
+ return left.length === right.length && timingSafeEqual(left, right);
18
+ }
19
+ export class BrowserExtensionBridge {
20
+ options;
21
+ server = null;
22
+ starting = null;
23
+ port = null;
24
+ accessToken = null;
25
+ origin = null;
26
+ pairing = null;
27
+ lastSeenAt = null;
28
+ paused = false;
29
+ waiter = null;
30
+ waiterTimer = null;
31
+ queue = [];
32
+ sent = new Map();
33
+ attempts = [];
34
+ nextAttemptAt = 0;
35
+ constructor(options) {
36
+ this.options = options;
37
+ const settingsFile = this.settingsFile();
38
+ if (existsSync(settingsFile))
39
+ chmodSync(settingsFile, 0o600);
40
+ const saved = readJsonOr(settingsFile, {});
41
+ if (typeof saved.accessToken === 'string' && /^[a-f0-9]{48}$/.test(saved.accessToken) && typeof saved.origin === 'string' && EXTENSION_ORIGIN.test(saved.origin)) {
42
+ this.accessToken = saved.accessToken;
43
+ this.origin = saved.origin;
44
+ }
45
+ this.paused = saved.paused === true;
46
+ if (Number.isInteger(saved.port) && saved.port > 0 && saved.port < 65536)
47
+ this.port = saved.port;
48
+ }
49
+ settingsFile() {
50
+ return join(this.options.stateDir, 'browser', 'extension-bridge.json');
51
+ }
52
+ save() {
53
+ const path = this.settingsFile();
54
+ mkdirSync(join(this.options.stateDir, 'browser'), { recursive: true });
55
+ if (!this.accessToken || !this.origin || !this.port) {
56
+ rmSync(path, { force: true });
57
+ return;
58
+ }
59
+ writeJson(path, { port: this.port, accessToken: this.accessToken, origin: this.origin, paused: this.paused });
60
+ chmodSync(path, 0o600);
61
+ }
62
+ status() {
63
+ const connected = Boolean(this.lastSeenAt && Date.now() - this.lastSeenAt <= GONE_MS);
64
+ const paired = Boolean(this.accessToken && this.origin);
65
+ const state = !this.server
66
+ ? 'stopped'
67
+ : this.paused && connected
68
+ ? 'paused'
69
+ : connected
70
+ ? 'connected'
71
+ : paired
72
+ ? 'waiting'
73
+ : 'pairing';
74
+ return { state, paired, connected, paused: this.paused, port: this.server ? this.port : null, lastSeenAt: connected ? this.lastSeenAt : null };
75
+ }
76
+ /** Starts the loopback bridge and returns a short-lived code for the extension's explicit pair action. */
77
+ start() {
78
+ if (this.server)
79
+ return Promise.resolve(this.startResult());
80
+ this.starting ??= this.listen().finally(() => { this.starting = null; });
81
+ return this.starting;
82
+ }
83
+ startResult() {
84
+ if (this.accessToken && this.origin)
85
+ return { status: this.status() };
86
+ if (!this.pairing || this.pairing.expiresAt <= Date.now()) {
87
+ this.pairing = { code: randomBytes(6).toString('hex').toUpperCase(), expiresAt: Date.now() + PAIR_MS };
88
+ }
89
+ return {
90
+ status: this.status(),
91
+ pairingCode: `${this.port}:${this.pairing.code}`,
92
+ expiresAt: this.pairing.expiresAt
93
+ };
94
+ }
95
+ async listen() {
96
+ const savedPort = this.options.port ?? this.port ?? DEFAULT_PORT;
97
+ const server = createServer((request, response) => void this.handle(request, response));
98
+ await new Promise((resolve, reject) => {
99
+ server.once('error', reject);
100
+ server.listen(savedPort, '127.0.0.1', () => {
101
+ server.removeListener('error', reject);
102
+ resolve();
103
+ });
104
+ });
105
+ this.server = server;
106
+ this.port = server.address().port;
107
+ this.save();
108
+ return this.startResult();
109
+ }
110
+ /** One request from the browser at a time; the extension's poll loop serializes the live tabs. */
111
+ request(operation) {
112
+ if (!this.server || !this.accessToken || !this.origin || !this.status().connected) {
113
+ return Promise.reject(new Error('Brave is not connected. Connect the extension in Browser settings; nothing was done.'));
114
+ }
115
+ if (this.paused)
116
+ return Promise.reject(new Error('Brave control is paused by the user. Resume it from the page badge; nothing was done.'));
117
+ return new Promise((resolve, reject) => {
118
+ const pending = {
119
+ id: randomUUID(), operation, resolve, reject,
120
+ timer: setTimeout(() => {
121
+ this.sent.delete(pending.id);
122
+ const queued = this.queue.indexOf(pending);
123
+ if (queued >= 0)
124
+ this.queue.splice(queued, 1);
125
+ reject(new Error('Brave did not answer before the browser call timed out. Its outcome is unknown; take a fresh snapshot before trying again.'));
126
+ }, REQUEST_MS)
127
+ };
128
+ pending.timer.unref?.();
129
+ this.queue.push(pending);
130
+ this.flush();
131
+ });
132
+ }
133
+ async stop() {
134
+ this.revoke();
135
+ await this.closeServer();
136
+ return this.status();
137
+ }
138
+ /** Keeps pairing across a normal Harness shutdown so the extension can reconnect to this profile. */
139
+ async close() {
140
+ this.lastSeenAt = null;
141
+ this.rejectPending(new Error('The Harness stopped the Brave bridge.'), 503);
142
+ await this.closeServer();
143
+ }
144
+ revoke() {
145
+ const origin = this.origin;
146
+ this.pairing = null;
147
+ this.accessToken = null;
148
+ this.origin = null;
149
+ this.lastSeenAt = null;
150
+ this.paused = false;
151
+ this.save();
152
+ this.rejectPending(new Error('Brave control was stopped by the user.'), 410, origin);
153
+ }
154
+ rejectPending(error, status = 410, origin = this.origin) {
155
+ if (this.waiter)
156
+ this.json(this.waiter, status, { error: status === 410 ? 'bridge_stopped' : 'bridge_unavailable' }, origin ?? undefined);
157
+ this.waiter = null;
158
+ if (this.waiterTimer)
159
+ clearTimeout(this.waiterTimer);
160
+ this.waiterTimer = null;
161
+ for (const pending of [...this.queue, ...this.sent.values()]) {
162
+ clearTimeout(pending.timer);
163
+ pending.reject(error);
164
+ }
165
+ this.queue.length = 0;
166
+ this.sent.clear();
167
+ }
168
+ async closeServer() {
169
+ const server = this.server;
170
+ this.server = null;
171
+ this.port = null;
172
+ if (!server)
173
+ return;
174
+ await new Promise((resolve) => server.close(() => resolve()));
175
+ }
176
+ flush() {
177
+ if (!this.waiter || !this.queue.length)
178
+ return;
179
+ const pending = this.queue.shift();
180
+ this.sent.set(pending.id, pending);
181
+ const waiter = this.waiter;
182
+ this.waiter = null;
183
+ if (this.waiterTimer)
184
+ clearTimeout(this.waiterTimer);
185
+ this.waiterTimer = null;
186
+ this.json(waiter, 200, { id: pending.id, operation: pending.operation });
187
+ }
188
+ json(response, status, body, origin) {
189
+ response.writeHead(status, {
190
+ 'content-type': 'application/json; charset=utf-8',
191
+ 'cache-control': 'no-store',
192
+ 'access-control-allow-origin': origin ?? 'null',
193
+ 'access-control-allow-methods': 'GET, POST, OPTIONS',
194
+ 'access-control-allow-headers': 'Authorization, Content-Type',
195
+ 'access-control-max-age': '60',
196
+ vary: 'Origin'
197
+ });
198
+ response.end(body === undefined ? undefined : JSON.stringify(body));
199
+ }
200
+ async body(request) {
201
+ let size = 0;
202
+ const chunks = [];
203
+ for await (const chunk of request) {
204
+ size += chunk.length;
205
+ if (size > MAX_BODY)
206
+ return null;
207
+ chunks.push(chunk);
208
+ }
209
+ try {
210
+ const value = JSON.parse(Buffer.concat(chunks).toString('utf8'));
211
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
212
+ }
213
+ catch {
214
+ return null;
215
+ }
216
+ }
217
+ originAllowed(origin, pairing = false) {
218
+ if (!origin || !EXTENSION_ORIGIN.test(origin))
219
+ return false;
220
+ return pairing || origin === this.origin;
221
+ }
222
+ // Extension service-worker fetches may omit Origin; the bearer token remains mandatory.
223
+ authorized(request, origin) {
224
+ if (!this.accessToken || (origin !== null && !this.originAllowed(origin)))
225
+ return false;
226
+ const authorization = request.headers.authorization ?? '';
227
+ const token = authorization.startsWith('Bearer ') ? authorization.slice(7) : '';
228
+ return sameSecret(token, this.accessToken);
229
+ }
230
+ async handle(request, response) {
231
+ try {
232
+ const port = this.port;
233
+ if (!port || request.headers.host !== `127.0.0.1:${port}`)
234
+ return this.json(response, 421);
235
+ const origin = typeof request.headers.origin === 'string' ? request.headers.origin : null;
236
+ const url = new URL(request.url ?? '/', `http://127.0.0.1:${port}`);
237
+ if (request.method === 'OPTIONS') {
238
+ const pairing = url.pathname === '/pair';
239
+ if (!this.originAllowed(origin, pairing))
240
+ return this.json(response, 403);
241
+ return this.json(response, 204, undefined, origin);
242
+ }
243
+ if (request.method === 'POST' && url.pathname === '/pair') {
244
+ if (!this.originAllowed(origin, true))
245
+ return this.json(response, 403);
246
+ if (Date.now() < this.nextAttemptAt)
247
+ return this.json(response, 429, { error: 'rate_limited' }, origin);
248
+ const body = await this.body(request);
249
+ const supplied = bounded(body?.code, 40);
250
+ const expected = this.pairing?.code ?? '';
251
+ const valid = Boolean(expected && this.pairing && this.pairing.expiresAt > Date.now() && sameSecret(supplied, expected));
252
+ if (!valid) {
253
+ this.attempts.push(Date.now());
254
+ this.attempts = this.attempts.filter((at) => Date.now() - at < 60_000);
255
+ if (this.attempts.length >= 5)
256
+ this.nextAttemptAt = Date.now() + 60_000;
257
+ return this.json(response, this.attempts.length >= 5 ? 429 : 403, { error: 'invalid_pairing_code' }, origin);
258
+ }
259
+ this.pairing = null;
260
+ this.attempts = [];
261
+ this.nextAttemptAt = 0;
262
+ this.accessToken = randomBytes(24).toString('hex');
263
+ this.origin = origin;
264
+ this.lastSeenAt = null;
265
+ this.paused = false;
266
+ this.save();
267
+ return this.json(response, 200, { token: this.accessToken }, origin);
268
+ }
269
+ if (!this.authorized(request, origin))
270
+ return this.json(response, 403);
271
+ if (request.method === 'GET' && url.pathname === '/status')
272
+ return this.json(response, 200, this.status(), origin);
273
+ if (request.method === 'GET' && url.pathname === '/poll') {
274
+ this.lastSeenAt = Date.now();
275
+ if (this.waiter)
276
+ this.json(this.waiter, 409, { error: 'poll_replaced' }, origin);
277
+ this.waiter = response;
278
+ if (this.waiterTimer)
279
+ clearTimeout(this.waiterTimer);
280
+ this.waiterTimer = setTimeout(() => {
281
+ if (this.waiter !== response)
282
+ return;
283
+ this.waiter = null;
284
+ this.json(response, 204, undefined, origin);
285
+ }, POLL_MS);
286
+ this.waiterTimer.unref?.();
287
+ this.flush();
288
+ return;
289
+ }
290
+ if (request.method === 'POST' && url.pathname === '/reply') {
291
+ const body = await this.body(request);
292
+ const id = bounded(body?.id, 80);
293
+ const pending = this.sent.get(id);
294
+ if (!pending)
295
+ return this.json(response, 404, { error: 'unknown_request' }, origin);
296
+ this.sent.delete(id);
297
+ clearTimeout(pending.timer);
298
+ pending.resolve(body?.result);
299
+ return this.json(response, 200, { ok: true }, origin);
300
+ }
301
+ if (request.method === 'POST' && url.pathname === '/control') {
302
+ const body = await this.body(request);
303
+ const action = body?.action;
304
+ if (action === 'pause') {
305
+ this.paused = true;
306
+ this.save();
307
+ for (const pending of this.queue.splice(0)) {
308
+ clearTimeout(pending.timer);
309
+ pending.reject(new Error('Brave control was paused by the user; nothing was done.'));
310
+ }
311
+ }
312
+ else if (action === 'resume') {
313
+ this.paused = false;
314
+ this.save();
315
+ }
316
+ else if (action === 'stop') {
317
+ this.revoke();
318
+ const status = this.status();
319
+ this.json(response, 200, { status }, origin);
320
+ setImmediate(() => void this.closeServer());
321
+ return;
322
+ }
323
+ else
324
+ return this.json(response, 400, { error: 'invalid_action' }, origin);
325
+ return this.json(response, 200, { status: this.status() }, origin);
326
+ }
327
+ this.json(response, 404, { error: 'not_found' }, origin);
328
+ }
329
+ catch {
330
+ if (!response.headersSent)
331
+ this.json(response, 500, { error: 'bridge_error' });
332
+ else
333
+ response.end();
334
+ }
335
+ }
336
+ }
@@ -0,0 +1,50 @@
1
+ import type { NetworkPolicy } from "../network.js";
2
+ import type { Download } from "../session.js";
3
+ import type { BrowserToolSession, BrowserToolTab } from "../tools.js";
4
+ import type { BrowserExtensionBridge } from "./bridge.js";
5
+ interface RemoteTab {
6
+ tabId: number;
7
+ url: string;
8
+ title: string;
9
+ generation: number;
10
+ active: boolean;
11
+ }
12
+ export declare class BraveBrowserSession implements BrowserToolSession {
13
+ private readonly options;
14
+ private readonly tabs;
15
+ private current;
16
+ private queue;
17
+ private idle;
18
+ private readonly idleMs;
19
+ readonly downloads: Download[];
20
+ readonly profileNote = "Brave is using your existing profile. Only normal tabs on sites you allowed in the extension are available; Incognito tabs are never exposed.";
21
+ constructor(options: {
22
+ bridge: BrowserExtensionBridge;
23
+ network: NetworkPolicy;
24
+ idleMs: number;
25
+ onIdle: () => void;
26
+ });
27
+ run<T>(work: () => Promise<T>): Promise<T>;
28
+ private touch;
29
+ invoke<T>(operation: Record<string, unknown>): Promise<T>;
30
+ private idFor;
31
+ private refreshTabs;
32
+ refresh(): Promise<void>;
33
+ refreshOne(id: string): Promise<RemoteTabState>;
34
+ get(id: string): RemoteTabState;
35
+ tab(id?: string | null): Promise<RemoteTabState>;
36
+ open(): Promise<RemoteTabState>;
37
+ select(id: string): Promise<RemoteTabState>;
38
+ list(): {
39
+ id: string;
40
+ url: string;
41
+ current: boolean;
42
+ }[];
43
+ get network(): NetworkPolicy;
44
+ get started(): boolean;
45
+ close(): Promise<void>;
46
+ }
47
+ interface RemoteTabState extends BrowserToolTab {
48
+ remote: RemoteTab;
49
+ }
50
+ export {};