@bbclaw/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/index.d.ts +8 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +8 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/providers/anthropic/index.d.ts +44 -0
  6. package/dist/providers/anthropic/index.d.ts.map +1 -0
  7. package/dist/providers/anthropic/index.js +75 -0
  8. package/dist/providers/anthropic/index.js.map +1 -0
  9. package/dist/providers/index.d.ts +8 -0
  10. package/dist/providers/index.d.ts.map +1 -0
  11. package/dist/providers/index.js +8 -0
  12. package/dist/providers/index.js.map +1 -0
  13. package/dist/providers/openai-compat/index.d.ts +59 -0
  14. package/dist/providers/openai-compat/index.d.ts.map +1 -0
  15. package/dist/providers/openai-compat/index.js +175 -0
  16. package/dist/providers/openai-compat/index.js.map +1 -0
  17. package/dist/providers/openai-compat/presets.d.ts +22 -0
  18. package/dist/providers/openai-compat/presets.d.ts.map +1 -0
  19. package/dist/providers/openai-compat/presets.js +73 -0
  20. package/dist/providers/openai-compat/presets.js.map +1 -0
  21. package/dist/providers/openai-compat/requestTranslate.d.ts +39 -0
  22. package/dist/providers/openai-compat/requestTranslate.d.ts.map +1 -0
  23. package/dist/providers/openai-compat/requestTranslate.js +228 -0
  24. package/dist/providers/openai-compat/requestTranslate.js.map +1 -0
  25. package/dist/providers/openai-compat/sseParser.d.ts +29 -0
  26. package/dist/providers/openai-compat/sseParser.d.ts.map +1 -0
  27. package/dist/providers/openai-compat/sseParser.js +139 -0
  28. package/dist/providers/openai-compat/sseParser.js.map +1 -0
  29. package/dist/providers/openai-compat/streamAdapter.d.ts +45 -0
  30. package/dist/providers/openai-compat/streamAdapter.d.ts.map +1 -0
  31. package/dist/providers/openai-compat/streamAdapter.js +233 -0
  32. package/dist/providers/openai-compat/streamAdapter.js.map +1 -0
  33. package/dist/providers/openai-compat/types.d.ts +126 -0
  34. package/dist/providers/openai-compat/types.d.ts.map +1 -0
  35. package/dist/providers/openai-compat/types.js +11 -0
  36. package/dist/providers/openai-compat/types.js.map +1 -0
  37. package/dist/providers/selector.d.ts +52 -0
  38. package/dist/providers/selector.d.ts.map +1 -0
  39. package/dist/providers/selector.js +101 -0
  40. package/dist/providers/selector.js.map +1 -0
  41. package/dist/providers/types.d.ts +114 -0
  42. package/dist/providers/types.d.ts.map +1 -0
  43. package/dist/providers/types.js +152 -0
  44. package/dist/providers/types.js.map +1 -0
  45. package/dist/providers/web-bridge/deepseek/spec.d.ts +22 -0
  46. package/dist/providers/web-bridge/deepseek/spec.d.ts.map +1 -0
  47. package/dist/providers/web-bridge/deepseek/spec.js +70 -0
  48. package/dist/providers/web-bridge/deepseek/spec.js.map +1 -0
  49. package/dist/providers/web-bridge/historySerializer.d.ts +49 -0
  50. package/dist/providers/web-bridge/historySerializer.d.ts.map +1 -0
  51. package/dist/providers/web-bridge/historySerializer.js +152 -0
  52. package/dist/providers/web-bridge/historySerializer.js.map +1 -0
  53. package/dist/providers/web-bridge/index.d.ts +10 -0
  54. package/dist/providers/web-bridge/index.d.ts.map +1 -0
  55. package/dist/providers/web-bridge/index.js +10 -0
  56. package/dist/providers/web-bridge/index.js.map +1 -0
  57. package/dist/providers/web-bridge/promptInjector.d.ts +63 -0
  58. package/dist/providers/web-bridge/promptInjector.d.ts.map +1 -0
  59. package/dist/providers/web-bridge/promptInjector.js +189 -0
  60. package/dist/providers/web-bridge/promptInjector.js.map +1 -0
  61. package/dist/providers/web-bridge/provider.d.ts +59 -0
  62. package/dist/providers/web-bridge/provider.d.ts.map +1 -0
  63. package/dist/providers/web-bridge/provider.js +176 -0
  64. package/dist/providers/web-bridge/provider.js.map +1 -0
  65. package/dist/providers/web-bridge/shared/BrowserSession.d.ts +51 -0
  66. package/dist/providers/web-bridge/shared/BrowserSession.d.ts.map +1 -0
  67. package/dist/providers/web-bridge/shared/BrowserSession.js +88 -0
  68. package/dist/providers/web-bridge/shared/BrowserSession.js.map +1 -0
  69. package/dist/providers/web-bridge/shared/WebBridgeAdapter.d.ts +97 -0
  70. package/dist/providers/web-bridge/shared/WebBridgeAdapter.d.ts.map +1 -0
  71. package/dist/providers/web-bridge/shared/WebBridgeAdapter.js +359 -0
  72. package/dist/providers/web-bridge/shared/WebBridgeAdapter.js.map +1 -0
  73. package/dist/providers/web-bridge/shared/observerScript.d.ts +41 -0
  74. package/dist/providers/web-bridge/shared/observerScript.d.ts.map +1 -0
  75. package/dist/providers/web-bridge/shared/observerScript.js +138 -0
  76. package/dist/providers/web-bridge/shared/observerScript.js.map +1 -0
  77. package/dist/providers/web-bridge/shared/types.d.ts +94 -0
  78. package/dist/providers/web-bridge/shared/types.d.ts.map +1 -0
  79. package/dist/providers/web-bridge/shared/types.js +25 -0
  80. package/dist/providers/web-bridge/shared/types.js.map +1 -0
  81. package/dist/providers/web-bridge/toolUseParser.d.ts +70 -0
  82. package/dist/providers/web-bridge/toolUseParser.d.ts.map +1 -0
  83. package/dist/providers/web-bridge/toolUseParser.js +360 -0
  84. package/dist/providers/web-bridge/toolUseParser.js.map +1 -0
  85. package/package.json +36 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BrowserSession.js","sourceRoot":"","sources":["../../../../src/providers/web-bridge/shared/BrowserSession.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * WebBridgeAdapter — service-agnostic driver for chat-web targets.
3
+ *
4
+ * Given a {@link WebBridgeSpec}, this class manages the browser session,
5
+ * injects the DOM observer, and exposes a high-level streaming API for the
6
+ * upper layers (WebBridgeProvider). Adding a new service (Kimi, ChatGPT, ...)
7
+ * is just providing another spec — no subclassing needed.
8
+ *
9
+ * Lifecycle:
10
+ * 1. start() — open the browser, navigate, inject observer.
11
+ * 2. isLoggedIn() — verify the composer is reachable.
12
+ * 3. newChat() — try locator strategies, fall back to URL nav.
13
+ * 4. askStream(prompt) — fill composer, submit, yield streaming text.
14
+ * 5. stop() — close the context.
15
+ *
16
+ * Concurrency model: one in-flight ask at a time. The upper layer is
17
+ * responsible for serializing turns; concurrent `askStream` calls reject
18
+ * with a clear error.
19
+ */
20
+ import { type Logger } from '@bbclaw/shared';
21
+ import { type BrowserSessionOptions } from './BrowserSession.js';
22
+ import { type ExternalUserMessageListener, type ResponseTextListener, type StateChangeListener, type WebBridgeSpec, type WebBridgeState } from './types.js';
23
+ export interface WebBridgeAdapterOptions {
24
+ /** Persistent data dir (cookies, localStorage). */
25
+ userDataDir: string;
26
+ /** Default to headless. */
27
+ headless?: boolean;
28
+ /** Throw if no first response text arrives within this many ms. */
29
+ askTimeoutMs?: number;
30
+ /**
31
+ * After the response text stops changing for this many ms, declare the
32
+ * turn done. Defaults to 2_000ms — DeepSeek-class services have small
33
+ * inter-token gaps but multi-second pauses while rendering markdown.
34
+ */
35
+ stabilityWindowMs?: number;
36
+ /** Logger; defaults to silent. */
37
+ logger?: Logger;
38
+ /** Customize browser session options (user agent, viewport, etc). */
39
+ browserSessionOptions?: Partial<Omit<BrowserSessionOptions, 'userDataDir' | 'headless'>>;
40
+ }
41
+ export declare class WebBridgeAdapter {
42
+ private readonly spec;
43
+ private readonly opts;
44
+ private readonly session;
45
+ private page;
46
+ private inFlight;
47
+ /** Messages we sent ourselves and haven't yet seen reflected in DOM — used to skip our own as "external". */
48
+ private pendingOwnUserMessages;
49
+ /** Cumulative text across all observed deltas for the current turn. */
50
+ private currentTurnText;
51
+ /** Stability timer that fires when text stops changing. */
52
+ private stabilityTimer;
53
+ /** Observer-exposed callback, registered once per context. */
54
+ private observerExposed;
55
+ private responseListeners;
56
+ private externalListeners;
57
+ private stateListeners;
58
+ constructor(spec: WebBridgeSpec, opts: WebBridgeAdapterOptions);
59
+ get serviceId(): string;
60
+ get displayName(): string;
61
+ get isRunning(): boolean;
62
+ getState(): WebBridgeState;
63
+ /** Current URL the page is showing, or undefined if no page open. */
64
+ currentUrl(): string | undefined;
65
+ /** Start the browser, navigate to home, install observer. Idempotent. */
66
+ start(): Promise<void>;
67
+ stop(): Promise<void>;
68
+ /** Toggle visibility. The browser is restarted on the next start() call. */
69
+ setHeadless(headless: boolean): Promise<void>;
70
+ /**
71
+ * Probe whether the composer is visible (i.e. user is logged in and the
72
+ * service is reachable). Starts the bridge if needed.
73
+ */
74
+ isLoggedIn(timeoutMs?: number): Promise<boolean>;
75
+ /**
76
+ * Click the service's "new chat" button using its fallback ladder. If
77
+ * every locator strategy fails, fall back to a fresh navigation to the
78
+ * home URL — most services treat that as starting a new conversation.
79
+ */
80
+ newChat(): Promise<void>;
81
+ /**
82
+ * Send a prompt and return an async iterable of cumulative text + done flag.
83
+ * The caller is responsible for diffing to deltas.
84
+ */
85
+ askStream(prompt: string): AsyncIterable<{
86
+ text: string;
87
+ done: boolean;
88
+ }>;
89
+ onResponseProgress(cb: ResponseTextListener): () => void;
90
+ onExternalUserMessage(cb: ExternalUserMessageListener): () => void;
91
+ onStateChange(cb: StateChangeListener): () => void;
92
+ private cleanupAsk;
93
+ private emitState;
94
+ private installObserver;
95
+ private handleObserverEvent;
96
+ }
97
+ //# sourceMappingURL=WebBridgeAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebBridgeAdapter.d.ts","sourceRoot":"","sources":["../../../../src/providers/web-bridge/shared/WebBridgeAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAkB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAEhF,OAAO,EAGL,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,KAAK,cAAc,EACpB,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,uBAAuB;IACtC,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAA;IACnB,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qEAAqE;IACrE,qBAAqB,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,qBAAqB,EAAE,aAAa,GAAG,UAAU,CAAC,CAAC,CAAA;CACzF;AAgBD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAGpB;IAED,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,IAAI,CAAoB;IAEhC,OAAO,CAAC,QAAQ,CAA2B;IAC3C,6GAA6G;IAC7G,OAAO,CAAC,sBAAsB,CAAI;IAClC,uEAAuE;IACvE,OAAO,CAAC,eAAe,CAAK;IAC5B,2DAA2D;IAC3D,OAAO,CAAC,cAAc,CAA8B;IACpD,8DAA8D;IAC9D,OAAO,CAAC,eAAe,CAAQ;IAE/B,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,iBAAiB,CAAoC;IAC7D,OAAO,CAAC,cAAc,CAA4B;gBAEtC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,uBAAuB;IAmB9D,IAAI,SAAS,IAAI,MAAM,CAEtB;IACD,IAAI,WAAW,IAAI,MAAM,CAExB;IACD,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,QAAQ,IAAI,cAAc;IAS1B,qEAAqE;IACrE,UAAU,IAAI,MAAM,GAAG,SAAS;IAKhC,yEAAyE;IACnE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAYtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B,4EAA4E;IACtE,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAQnD;;;OAGG;IACG,UAAU,CAAC,SAAS,SAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAcrD;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B9B;;;OAGG;IACI,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;IAyGhF,kBAAkB,CAAC,EAAE,EAAE,oBAAoB,GAAG,MAAM,IAAI;IAOxD,qBAAqB,CAAC,EAAE,EAAE,2BAA2B,GAAG,MAAM,IAAI;IAOlE,aAAa,CAAC,EAAE,EAAE,mBAAmB,GAAG,MAAM,IAAI;IASlD,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,SAAS;YAKH,eAAe;IAoB7B,OAAO,CAAC,mBAAmB;CAsC5B"}
@@ -0,0 +1,359 @@
1
+ /**
2
+ * WebBridgeAdapter — service-agnostic driver for chat-web targets.
3
+ *
4
+ * Given a {@link WebBridgeSpec}, this class manages the browser session,
5
+ * injects the DOM observer, and exposes a high-level streaming API for the
6
+ * upper layers (WebBridgeProvider). Adding a new service (Kimi, ChatGPT, ...)
7
+ * is just providing another spec — no subclassing needed.
8
+ *
9
+ * Lifecycle:
10
+ * 1. start() — open the browser, navigate, inject observer.
11
+ * 2. isLoggedIn() — verify the composer is reachable.
12
+ * 3. newChat() — try locator strategies, fall back to URL nav.
13
+ * 4. askStream(prompt) — fill composer, submit, yield streaming text.
14
+ * 5. stop() — close the context.
15
+ *
16
+ * Concurrency model: one in-flight ask at a time. The upper layer is
17
+ * responsible for serializing turns; concurrent `askStream` calls reject
18
+ * with a clear error.
19
+ */
20
+ import { silentLogger } from '@bbclaw/shared';
21
+ import { BrowserSession } from './BrowserSession.js';
22
+ import { buildObserverScriptSource } from './observerScript.js';
23
+ import { BridgeDOMUnrecognizedError, BridgeLoginRequiredError, } from './types.js';
24
+ const DEFAULT_STABILITY_MS = 2_000;
25
+ const DEFAULT_ASK_TIMEOUT_MS = 180_000;
26
+ export class WebBridgeAdapter {
27
+ spec;
28
+ opts;
29
+ session;
30
+ page = null;
31
+ inFlight = null;
32
+ /** Messages we sent ourselves and haven't yet seen reflected in DOM — used to skip our own as "external". */
33
+ pendingOwnUserMessages = 0;
34
+ /** Cumulative text across all observed deltas for the current turn. */
35
+ currentTurnText = '';
36
+ /** Stability timer that fires when text stops changing. */
37
+ stabilityTimer = null;
38
+ /** Observer-exposed callback, registered once per context. */
39
+ observerExposed = false;
40
+ responseListeners = [];
41
+ externalListeners = [];
42
+ stateListeners = [];
43
+ constructor(spec, opts) {
44
+ this.spec = spec;
45
+ this.opts = {
46
+ userDataDir: opts.userDataDir,
47
+ headless: opts.headless ?? true,
48
+ askTimeoutMs: opts.askTimeoutMs ?? DEFAULT_ASK_TIMEOUT_MS,
49
+ stabilityWindowMs: opts.stabilityWindowMs ?? DEFAULT_STABILITY_MS,
50
+ browserSessionOptions: opts.browserSessionOptions ?? {},
51
+ logger: opts.logger ?? silentLogger,
52
+ };
53
+ this.session = new BrowserSession({
54
+ userDataDir: this.opts.userDataDir,
55
+ headless: this.opts.headless,
56
+ ...this.opts.browserSessionOptions,
57
+ });
58
+ }
59
+ // ---- public surface ----
60
+ get serviceId() {
61
+ return this.spec.serviceId;
62
+ }
63
+ get displayName() {
64
+ return this.spec.displayName;
65
+ }
66
+ get isRunning() {
67
+ return this.session.isOpen;
68
+ }
69
+ getState() {
70
+ return {
71
+ running: this.session.isOpen,
72
+ headless: this.session.headless,
73
+ busy: this.inFlight !== null,
74
+ loggedIn: 'unknown',
75
+ };
76
+ }
77
+ /** Current URL the page is showing, or undefined if no page open. */
78
+ currentUrl() {
79
+ if (!this.page || this.page.isClosed())
80
+ return undefined;
81
+ return this.page.url();
82
+ }
83
+ /** Start the browser, navigate to home, install observer. Idempotent. */
84
+ async start() {
85
+ if (this.page && !this.page.isClosed())
86
+ return;
87
+ this.page = await this.session.ensurePage(this.spec.homeUrl);
88
+ this.page.on('close', () => {
89
+ if (this.inFlight)
90
+ this.inFlight.reject(new Error('Web bridge: page closed mid-turn'));
91
+ this.page = null;
92
+ this.emitState();
93
+ });
94
+ await this.installObserver(this.page);
95
+ this.emitState();
96
+ }
97
+ async stop() {
98
+ if (this.inFlight)
99
+ this.inFlight.reject(new Error('Web bridge stopped'));
100
+ this.observerExposed = false;
101
+ await this.session.close();
102
+ this.page = null;
103
+ this.emitState();
104
+ }
105
+ /** Toggle visibility. The browser is restarted on the next start() call. */
106
+ async setHeadless(headless) {
107
+ if (this.session.headless === headless && this.session.isOpen)
108
+ return;
109
+ await this.session.restart(headless);
110
+ this.observerExposed = false;
111
+ this.page = null;
112
+ this.emitState();
113
+ }
114
+ /**
115
+ * Probe whether the composer is visible (i.e. user is logged in and the
116
+ * service is reachable). Starts the bridge if needed.
117
+ */
118
+ async isLoggedIn(timeoutMs = 3_000) {
119
+ await this.start();
120
+ if (!this.page || this.page.isClosed())
121
+ return false;
122
+ try {
123
+ await this.page.waitForSelector(this.spec.selectors.composer, {
124
+ state: 'visible',
125
+ timeout: timeoutMs,
126
+ });
127
+ return true;
128
+ }
129
+ catch {
130
+ return false;
131
+ }
132
+ }
133
+ /**
134
+ * Click the service's "new chat" button using its fallback ladder. If
135
+ * every locator strategy fails, fall back to a fresh navigation to the
136
+ * home URL — most services treat that as starting a new conversation.
137
+ */
138
+ async newChat() {
139
+ await this.start();
140
+ const page = this.page;
141
+ if (!page)
142
+ throw new Error('Web bridge: cannot newChat without a page');
143
+ for (const strategy of this.spec.newChatStrategies) {
144
+ try {
145
+ const loc = strategy(page);
146
+ await loc.click({ timeout: 1_500 });
147
+ // After clicking new chat, the observer's turnStartIdx self-resets via the userCount drop.
148
+ this.currentTurnText = '';
149
+ return;
150
+ }
151
+ catch {
152
+ // try next strategy
153
+ }
154
+ }
155
+ // Last resort.
156
+ try {
157
+ await page.goto(this.spec.homeUrl, { waitUntil: 'domcontentloaded', timeout: 8_000 });
158
+ this.currentTurnText = '';
159
+ return;
160
+ }
161
+ catch (e) {
162
+ throw new BridgeDOMUnrecognizedError(`Couldn't start new chat on ${this.spec.serviceId} — all fallbacks (including URL navigation) failed. Underlying: ${e instanceof Error ? e.message : String(e)}`);
163
+ }
164
+ }
165
+ /**
166
+ * Send a prompt and return an async iterable of cumulative text + done flag.
167
+ * The caller is responsible for diffing to deltas.
168
+ */
169
+ async *askStream(prompt) {
170
+ await this.start();
171
+ const page = this.page;
172
+ if (!page)
173
+ throw new Error('Web bridge: cannot ask without a page');
174
+ if (this.inFlight)
175
+ throw new Error('Web bridge: another ask is in flight');
176
+ // Verify composer reachable; surface login-required cleanly.
177
+ try {
178
+ await page.waitForSelector(this.spec.selectors.composer, { state: 'visible', timeout: 8_000 });
179
+ }
180
+ catch {
181
+ throw new BridgeLoginRequiredError(`${this.spec.displayName} composer not visible — service likely needs login.`);
182
+ }
183
+ let resolveOne = null;
184
+ const queue = [];
185
+ const push = (item) => {
186
+ if (resolveOne) {
187
+ const fn = resolveOne;
188
+ resolveOne = null;
189
+ fn(item);
190
+ }
191
+ else {
192
+ queue.push(item);
193
+ }
194
+ };
195
+ const progressEmit = (text, done) => {
196
+ push({ kind: 'progress', text, done });
197
+ };
198
+ const ask = {
199
+ resolve: () => progressEmit(this.currentTurnText, true),
200
+ reject: (err) => {
201
+ this.opts.logger.warn(`[${this.spec.serviceId}] askStream rejected:`, err.message);
202
+ // Surface the error to the consumer via the queue. The generator will
203
+ // see it and re-throw, instead of silently completing with empty text.
204
+ push({ kind: 'error', error: err });
205
+ },
206
+ lastText: '',
207
+ watchdog: setTimeout(() => {
208
+ if (this.inFlight !== ask)
209
+ return;
210
+ if (ask.lastText) {
211
+ ask.resolve();
212
+ }
213
+ else {
214
+ ask.reject(new Error(`Web bridge: response timeout after ${this.opts.askTimeoutMs}ms — no text observed. ` +
215
+ `Likely causes: DOM selectors out of date (responseRoot did not match anything), the service is showing a captcha, or the request was silently dropped. ` +
216
+ `Run with --no-headless to see what's on screen.`));
217
+ }
218
+ }, this.opts.askTimeoutMs),
219
+ progressEmit,
220
+ };
221
+ this.inFlight = ask;
222
+ this.pendingOwnUserMessages++;
223
+ this.currentTurnText = '';
224
+ this.emitState();
225
+ // Fire-and-forget the actual send. We DON'T await it here because the
226
+ // browser-side observer will start firing events as soon as the response
227
+ // begins streaming; we want to consume those concurrently.
228
+ void (async () => {
229
+ try {
230
+ await page.fill(this.spec.selectors.composer, prompt);
231
+ await page.keyboard.press('Enter');
232
+ this.opts.logger.debug(`[${this.spec.serviceId}] prompt sent (${prompt.length} chars); waiting for response...`);
233
+ }
234
+ catch (e) {
235
+ ask.reject(new Error(`Web bridge: failed to send prompt — ${e instanceof Error ? e.message : String(e)}. ` +
236
+ `The composer (${this.spec.selectors.composer}) might have moved or become disabled.`));
237
+ }
238
+ })();
239
+ try {
240
+ while (true) {
241
+ const next = await new Promise((resolve) => {
242
+ if (queue.length > 0) {
243
+ resolve(queue.shift());
244
+ }
245
+ else {
246
+ resolveOne = resolve;
247
+ }
248
+ });
249
+ if (next.kind === 'error') {
250
+ throw next.error;
251
+ }
252
+ yield { text: next.text, done: next.done };
253
+ if (next.done)
254
+ return;
255
+ }
256
+ }
257
+ finally {
258
+ this.cleanupAsk(ask);
259
+ }
260
+ }
261
+ // ---- subscriptions ----
262
+ onResponseProgress(cb) {
263
+ this.responseListeners.push(cb);
264
+ return () => {
265
+ this.responseListeners = this.responseListeners.filter((f) => f !== cb);
266
+ };
267
+ }
268
+ onExternalUserMessage(cb) {
269
+ this.externalListeners.push(cb);
270
+ return () => {
271
+ this.externalListeners = this.externalListeners.filter((f) => f !== cb);
272
+ };
273
+ }
274
+ onStateChange(cb) {
275
+ this.stateListeners.push(cb);
276
+ return () => {
277
+ this.stateListeners = this.stateListeners.filter((f) => f !== cb);
278
+ };
279
+ }
280
+ // ---- internals ----
281
+ cleanupAsk(ask) {
282
+ if (this.inFlight !== ask)
283
+ return;
284
+ clearTimeout(ask.watchdog);
285
+ if (this.stabilityTimer) {
286
+ clearTimeout(this.stabilityTimer);
287
+ this.stabilityTimer = null;
288
+ }
289
+ this.inFlight = null;
290
+ this.emitState();
291
+ }
292
+ emitState() {
293
+ const s = this.getState();
294
+ for (const fn of this.stateListeners)
295
+ fn(s);
296
+ }
297
+ async installObserver(page) {
298
+ if (!this.observerExposed) {
299
+ try {
300
+ await page.exposeFunction('__bbclawBridgeEvent', (event) => {
301
+ this.handleObserverEvent(event);
302
+ });
303
+ this.observerExposed = true;
304
+ }
305
+ catch {
306
+ // Already exposed for this context.
307
+ this.observerExposed = true;
308
+ }
309
+ }
310
+ // We pass the script as a plain JS string (not a function) so it bypasses
311
+ // any compiler-added wrappers (esbuild's __name, etc.) that would crash
312
+ // in the page context.
313
+ const source = buildObserverScriptSource(this.spec.selectors);
314
+ await page.addInitScript({ content: source });
315
+ await page.evaluate(source);
316
+ }
317
+ handleObserverEvent(event) {
318
+ if (event.type === 'response-progress') {
319
+ const text = event.text ?? '';
320
+ if (text === this.currentTurnText)
321
+ return;
322
+ this.currentTurnText = text;
323
+ // Forward to passive listeners.
324
+ for (const fn of this.responseListeners)
325
+ fn(text, false);
326
+ // Forward to the in-flight ask, if any.
327
+ const ask = this.inFlight;
328
+ if (ask) {
329
+ ask.lastText = text;
330
+ ask.progressEmit(text, false);
331
+ }
332
+ // Reset the stability timer.
333
+ if (this.stabilityTimer)
334
+ clearTimeout(this.stabilityTimer);
335
+ this.stabilityTimer = setTimeout(() => {
336
+ this.stabilityTimer = null;
337
+ const finalText = this.currentTurnText;
338
+ for (const fn of this.responseListeners)
339
+ fn(finalText, true);
340
+ if (this.inFlight) {
341
+ this.inFlight.lastText = finalText;
342
+ this.inFlight.resolve();
343
+ }
344
+ }, this.opts.stabilityWindowMs);
345
+ }
346
+ else if (event.type === 'external-user-message') {
347
+ // Skip our own messages — they always echo via the observer first.
348
+ if (this.pendingOwnUserMessages > 0) {
349
+ this.pendingOwnUserMessages--;
350
+ return;
351
+ }
352
+ const text = event.text ?? '';
353
+ for (const fn of this.externalListeners)
354
+ fn(text);
355
+ }
356
+ // composer-ready: we currently don't act on this; future use for state machine.
357
+ }
358
+ }
359
+ //# sourceMappingURL=WebBridgeAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebBridgeAdapter.js","sourceRoot":"","sources":["../../../../src/providers/web-bridge/shared/WebBridgeAdapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Browser-side observer script (string template form).
3
+ *
4
+ * Why a string and not a function? When we pass a TypeScript function to
5
+ * Playwright's page.evaluate/addInitScript, it stringifies the function with
6
+ * Function.prototype.toString(). But tsx/esbuild (and other TS compilers)
7
+ * wrap functions with helpers like `__name` for name-reflection support.
8
+ * Those helpers don't exist in the page's browser context, so the injected
9
+ * script crashes with "__name is not defined".
10
+ *
11
+ * By writing the body as a string, we bypass the compiler entirely — what
12
+ * goes to the page is exactly what we wrote.
13
+ *
14
+ * The script is parameterized by selectors, injected as a JSON literal.
15
+ *
16
+ * It watches the DOM for two things:
17
+ * 1. New user-bubble appearances — each one marks a turn boundary and may
18
+ * indicate a user typed directly into the chat (passive capture).
19
+ * 2. Text changes in the response container that follows the latest user
20
+ * bubble — these are streamed back as response progress.
21
+ * It calls back into Node via the exposed function `__bbclawBridgeEvent`.
22
+ */
23
+ export interface BridgeObserverSelectors {
24
+ composer: string;
25
+ responseRoot: string;
26
+ userMessage: string;
27
+ }
28
+ export interface BridgeObserverEvent {
29
+ type: 'response-progress' | 'external-user-message' | 'composer-ready';
30
+ text?: string;
31
+ }
32
+ /**
33
+ * Build a self-contained JS source string that can be passed to Playwright's
34
+ * page.evaluate / page.addInitScript via the `{ content }` form.
35
+ *
36
+ * The selectors are inlined as a JSON literal, so the page receives a single
37
+ * standalone IIFE invocation — no compiler artifacts, no closures over
38
+ * Node-side values.
39
+ */
40
+ export declare function buildObserverScriptSource(selectors: BridgeObserverSelectors): string;
41
+ //# sourceMappingURL=observerScript.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observerScript.d.ts","sourceRoot":"","sources":["../../../../src/providers/web-bridge/shared/observerScript.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,mBAAmB,GAAG,uBAAuB,GAAG,gBAAgB,CAAA;IACtE,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AA2GD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,uBAAuB,GAAG,MAAM,CAEpF"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Browser-side observer script (string template form).
3
+ *
4
+ * Why a string and not a function? When we pass a TypeScript function to
5
+ * Playwright's page.evaluate/addInitScript, it stringifies the function with
6
+ * Function.prototype.toString(). But tsx/esbuild (and other TS compilers)
7
+ * wrap functions with helpers like `__name` for name-reflection support.
8
+ * Those helpers don't exist in the page's browser context, so the injected
9
+ * script crashes with "__name is not defined".
10
+ *
11
+ * By writing the body as a string, we bypass the compiler entirely — what
12
+ * goes to the page is exactly what we wrote.
13
+ *
14
+ * The script is parameterized by selectors, injected as a JSON literal.
15
+ *
16
+ * It watches the DOM for two things:
17
+ * 1. New user-bubble appearances — each one marks a turn boundary and may
18
+ * indicate a user typed directly into the chat (passive capture).
19
+ * 2. Text changes in the response container that follows the latest user
20
+ * bubble — these are streamed back as response progress.
21
+ * It calls back into Node via the exposed function `__bbclawBridgeEvent`.
22
+ */
23
+ const OBSERVER_BODY = String.raw `(function(selectors) {
24
+ if (window.__bbclawBridgeInstalled) return;
25
+
26
+ // addInitScript runs before any page scripts — document.body may not exist
27
+ // yet. Defer the actual installation until the body is available so that
28
+ // MutationObserver.observe doesn't throw on null. We set the installed flag
29
+ // only AFTER successful attach, so a transient race doesn't permanently
30
+ // mark the observer as installed.
31
+ function install() {
32
+ if (window.__bbclawBridgeInstalled) return;
33
+ if (!document.body) {
34
+ // Try again on next tick.
35
+ setTimeout(install, 50);
36
+ return;
37
+ }
38
+ window.__bbclawBridgeInstalled = true;
39
+
40
+ // The starting index of response nodes BEFORE the latest user turn.
41
+ // Everything at or after this index belongs to the in-progress / latest
42
+ // assistant turn.
43
+ var turnStartIdx = document.querySelectorAll(selectors.responseRoot).length;
44
+ var prevUserCount = document.querySelectorAll(selectors.userMessage).length;
45
+ var prevResponseText = '';
46
+
47
+ function isReasoningNode(el) {
48
+ // Walk up the ancestor chain — many services wrap CoT inside a collapsed
49
+ // <details> or a div with a "thinking"/"reasoning" class.
50
+ var cur = el;
51
+ while (cur && cur !== document.body) {
52
+ if (cur.tagName === 'DETAILS') return true;
53
+ var cls = typeof cur.className === 'string' ? cur.className.toLowerCase() : '';
54
+ if (/think|reason|thought|chain-of-thought|深度思考|思考/.test(cls)) return true;
55
+ cur = cur.parentElement;
56
+ }
57
+ return false;
58
+ }
59
+
60
+ function computeCurrentResponseText() {
61
+ var all = Array.prototype.slice.call(document.querySelectorAll(selectors.responseRoot));
62
+ var slice = all.slice(turnStartIdx);
63
+ if (slice.length === 0) return '';
64
+ // Drop wrappers — keep only leaves (nodes that don't contain other matched nodes).
65
+ var leaves = [];
66
+ for (var i = 0; i < slice.length; i++) {
67
+ var node = slice[i];
68
+ var hasInner = false;
69
+ for (var j = 0; j < slice.length; j++) {
70
+ if (i !== j && node.contains(slice[j])) { hasInner = true; break; }
71
+ }
72
+ if (!hasInner) leaves.push(node);
73
+ }
74
+ // Drop reasoning / CoT blocks; they're model-internal scratch.
75
+ var answers = [];
76
+ for (var k = 0; k < leaves.length; k++) {
77
+ if (!isReasoningNode(leaves[k])) answers.push(leaves[k]);
78
+ }
79
+ var parts = [];
80
+ for (var m = 0; m < answers.length; m++) parts.push(answers[m].innerText);
81
+ return parts.join('\n\n');
82
+ }
83
+
84
+ function emit(ev) {
85
+ if (typeof window.__bbclawBridgeEvent === 'function') {
86
+ window.__bbclawBridgeEvent(ev);
87
+ }
88
+ }
89
+
90
+ var obs = new MutationObserver(function() {
91
+ var userNodes = document.querySelectorAll(selectors.userMessage);
92
+
93
+ if (userNodes.length > prevUserCount) {
94
+ turnStartIdx = document.querySelectorAll(selectors.responseRoot).length;
95
+ for (var i = prevUserCount; i < userNodes.length; i++) {
96
+ emit({ type: 'external-user-message', text: userNodes[i].innerText });
97
+ }
98
+ prevUserCount = userNodes.length;
99
+ prevResponseText = '';
100
+ } else if (userNodes.length < prevUserCount) {
101
+ prevUserCount = userNodes.length;
102
+ turnStartIdx = document.querySelectorAll(selectors.responseRoot).length;
103
+ prevResponseText = '';
104
+ }
105
+
106
+ var text = computeCurrentResponseText();
107
+ if (text !== prevResponseText) {
108
+ prevResponseText = text;
109
+ emit({ type: 'response-progress', text: text });
110
+ }
111
+ });
112
+
113
+ obs.observe(document.body, { childList: true, subtree: true, characterData: true });
114
+
115
+ // Poll composer reachability for "logged-in" detection.
116
+ var composerCheck = setInterval(function() {
117
+ if (document.querySelector(selectors.composer)) {
118
+ emit({ type: 'composer-ready' });
119
+ clearInterval(composerCheck);
120
+ }
121
+ }, 200);
122
+ setTimeout(function() { clearInterval(composerCheck); }, 30000);
123
+ }
124
+
125
+ install();
126
+ })`;
127
+ /**
128
+ * Build a self-contained JS source string that can be passed to Playwright's
129
+ * page.evaluate / page.addInitScript via the `{ content }` form.
130
+ *
131
+ * The selectors are inlined as a JSON literal, so the page receives a single
132
+ * standalone IIFE invocation — no compiler artifacts, no closures over
133
+ * Node-side values.
134
+ */
135
+ export function buildObserverScriptSource(selectors) {
136
+ return `${OBSERVER_BODY}(${JSON.stringify(selectors)});`;
137
+ }
138
+ //# sourceMappingURL=observerScript.js.map