yiyan-browser-agent 1.0.6 → 1.0.8

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/browser.js +46 -90
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yiyan-browser-agent",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "AI coding agent powered by Yiyan (文心一言) via browser automation — no API key needed",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/browser.js CHANGED
@@ -228,51 +228,21 @@ class YiyanBrowser {
228
228
  });
229
229
  }
230
230
 
231
- // ── Sending Messages (optimized: paste instead of type) ────────────────────
231
+ // ── Sending Messages (stable: keyboard.type) ────────────────────────────────
232
232
 
233
233
  async sendMessage(text) {
234
234
  const { el } = await this._findInput();
235
235
 
236
- // Focus the element
237
- await el.focus();
236
+ // Focus and select all existing content
237
+ await el.click({ clickCount: 3, force: true });
238
+ await this.page.waitForTimeout(200);
238
239
 
239
- // Clear existing content
240
- await el.evaluate(e => {
241
- e.textContent = '';
242
- if (e.innerText) e.innerText = '';
243
- });
244
-
245
- // Use clipboard paste for instant input (much faster than typing)
246
- await this.page.evaluate(async (text) => {
247
- // Try modern clipboard API first
248
- try {
249
- await navigator.clipboard.writeText(text);
250
- document.execCommand('paste');
251
- return;
252
- } catch {}
253
-
254
- // Fallback: direct DOM manipulation with proper event dispatch
255
- const activeEl = document.activeElement;
256
- if (activeEl && activeEl.isContentEditable) {
257
- activeEl.textContent = text;
258
-
259
- // Dispatch input event so Yiyan recognizes the content
260
- const inputEvent = new InputEvent('input', {
261
- bubbles: true,
262
- cancelable: true,
263
- inputType: 'insertText',
264
- data: text,
265
- });
266
- activeEl.dispatchEvent(inputEvent);
267
-
268
- // Also dispatch change event for good measure
269
- const changeEvent = new Event('change', { bubbles: true });
270
- activeEl.dispatchEvent(changeEvent);
271
- }
272
- }, text);
240
+ // Clear by pressing Delete
241
+ await this.page.keyboard.press('Delete');
242
+ await this.page.waitForTimeout(100);
273
243
 
274
- // Small delay to let UI process the input
275
- await this.page.waitForTimeout(50);
244
+ // Type text character by character (stable, works reliably)
245
+ await this.page.keyboard.type(text, { delay: 30 });
276
246
 
277
247
  // Press Enter to send
278
248
  await this.page.keyboard.press('Enter');
@@ -297,73 +267,59 @@ class YiyanBrowser {
297
267
  );
298
268
  }
299
269
 
300
- // ── Waiting for Response (optimized: MutationObserver) ────────────────────
301
-
270
+ // ── Waiting for Response (stable polling) ────────────────────────────────────
271
+
272
+ /**
273
+ * Wait until Yiyan finishes generating and return the response text.
274
+ *
275
+ * Algorithm:
276
+ * 1. Record how many assistant messages are on the page right now.
277
+ * 2. Wait until a new message appears (count goes up).
278
+ * 3. Poll the last message text every 300 ms.
279
+ * 4. When the text has not changed for STABLE_DELAY ms AND
280
+ * no stop/loading indicator is visible → done.
281
+ */
302
282
  async waitForResponse() {
303
283
  const timeout = config.RESPONSE_TIMEOUT;
304
284
  const stableDelay = config.STABLE_DELAY;
305
285
  const start = Date.now();
306
286
 
307
- // Use MutationObserver for efficient change detection
308
- const responseId = await this.page.evaluate(() => {
309
- let lastText = '';
310
- let stableStart = null;
311
- let done = false;
312
-
313
- // Find response container
314
- const container = document.querySelector('[class*="answer"], [class*="response"], [class*="markdown"], .ds-markdown, [class*="content"]');
315
- if (!container) return null;
316
-
317
- const observer = new MutationObserver(() => {
318
- const currentText = container.textContent || '';
319
-
320
- if (currentText !== lastText) {
321
- lastText = currentText;
322
- stableStart = Date.now();
323
- } else if (stableStart && Date.now() - stableStart >= 2500) {
324
- // Text stable for 2.5s = done
325
- done = true;
326
- }
327
- });
287
+ // Phase 1: wait for a new message to appear
288
+ const initialCount = await this._getMessageCount();
289
+ let appeared = false;
328
290
 
329
- observer.observe(container, { childList: true, subtree: true, characterData: true });
291
+ for (let i = 0; i < 30; i++) {
292
+ const count = await this._getMessageCount();
293
+ if (count > initialCount) { appeared = true; break; }
294
+ await this.page.waitForTimeout(300);
295
+ }
330
296
 
331
- // Store for cleanup
332
- window.__yiyanObserver = observer;
333
- window.__yiyanDone = () => done;
334
- window.__yiyanText = () => lastText;
297
+ if (!appeared) logger.warn('Response may have been delayed — continuing to wait...');
335
298
 
336
- return container.className || 'response-container';
337
- });
299
+ // Phase 2: wait for text to stabilise
300
+ let lastText = '';
301
+ let stableStart = null;
302
+ let dotCount = 0;
338
303
 
339
- // Wait for completion signal
340
- let dotCount = 0;
341
304
  while (Date.now() - start < timeout) {
342
- const state = await this.page.evaluate(() => ({
343
- done : window.__yiyanDone ? window.__yiyanDone() : false,
344
- text : window.__yiyanText ? window.__yiyanText() : '',
345
- length: (window.__yiyanText ? window.__yiyanText() : '').length,
346
- }));
347
-
348
- if (state.done && state.length > 10 && !await this._isGenerating()) {
349
- break;
305
+ const text = await this._extractLastMessage();
306
+
307
+ if (text !== lastText) {
308
+ lastText = text;
309
+ stableStart = null;
310
+ } else if (text.length > 0) {
311
+ if (!stableStart) stableStart = Date.now();
312
+ else if (Date.now() - stableStart >= stableDelay) {
313
+ if (!await this._isGenerating()) break; // confirmed done
314
+ stableStart = null; // still generating, reset
315
+ }
350
316
  }
351
317
 
352
318
  // Progress indicator
353
319
  dotCount = (dotCount + 1) % 4;
354
- logger.thinking(`Receiving response${'.'.repeat(dotCount)} (${state.length} chars)`);
320
+ logger.thinking(`Receiving response${'.'.repeat(dotCount)} (${text.length} chars)`);
355
321
  }
356
322
 
357
- // Cleanup observer
358
- await this.page.evaluate(() => {
359
- if (window.__yiyanObserver) {
360
- window.__yiyanObserver.disconnect();
361
- delete window.__yiyanObserver;
362
- delete window.__yiyanDone;
363
- delete window.__yiyanText;
364
- }
365
- });
366
-
367
323
  logger.clearLine();
368
324
 
369
325
  const final = await this._extractLastMessage();