bunnyquery 1.3.4 → 1.3.5

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/bunnyquery.js CHANGED
@@ -1221,6 +1221,7 @@ ${options.inlineContentPlaceholder}
1221
1221
  this.enqueueTypewrite(aiIdx, answer, lid);
1222
1222
  }
1223
1223
  }
1224
+ this._removeStrayPendingAssistants();
1224
1225
  this.promoteNextQueuedToRunning();
1225
1226
  this.updateHistoryCache();
1226
1227
  this.host.notify();
@@ -1263,6 +1264,7 @@ ${options.inlineContentPlaceholder}
1263
1264
  if (thPos2 !== -1) this.state.messages.splice(thPos2, 1);
1264
1265
  }
1265
1266
  if (serverId) this.cancelledServerIds.delete(serverId);
1267
+ this._removeStrayPendingAssistants();
1266
1268
  this.promoteNextQueuedToRunning();
1267
1269
  this.updateHistoryCache();
1268
1270
  this.host.notify();
@@ -1276,6 +1278,7 @@ ${options.inlineContentPlaceholder}
1276
1278
  return;
1277
1279
  }
1278
1280
  this.insertAtTarget({ role: "assistant", content: getErrorMessage(err), isError: true }, targetIdx);
1281
+ this._removeStrayPendingAssistants();
1279
1282
  this.promoteNextQueuedToRunning();
1280
1283
  this.updateHistoryCache();
1281
1284
  this.host.notify();
@@ -1437,16 +1440,48 @@ ${options.inlineContentPlaceholder}
1437
1440
  if (pendingIdx === -1) return Promise.resolve();
1438
1441
  if (latest.isError || !latest.content) {
1439
1442
  this.state.messages[pendingIdx] = { role: "assistant", content: latest.content || "", isError: !!latest.isError };
1443
+ this._removeStrayPendingAssistants();
1440
1444
  this.host.notify();
1441
1445
  this.promoteNextQueuedToRunning();
1442
1446
  return Promise.resolve();
1443
1447
  }
1444
1448
  var lid = this._newLocalId();
1445
1449
  this.state.messages[pendingIdx] = { role: "assistant", content: "", isPending: false, _localId: lid };
1450
+ this._removeStrayPendingAssistants();
1446
1451
  this.host.notify();
1447
1452
  this.promoteNextQueuedToRunning();
1448
1453
  return this.enqueueTypewrite(pendingIdx, latest.content, lid);
1449
1454
  }
1455
+ // Remove any leftover non-background pending ("Thinking…") assistant bubbles.
1456
+ // There is normally at most ONE such bubble at a time (promoteNext* refuses to
1457
+ // add a second), so any extra is a duplicate — it appears when a concurrent
1458
+ // history refetch re-maps the still-"running" turn into a pending placeholder
1459
+ // (with a real _serverItemId) while the local pending bubble (no _serverItemId)
1460
+ // is rescued and re-appended (see loadHistory rescue below). Each resolve path
1461
+ // only replaces the FIRST pending bubble, so without this a stray "Thinking…"
1462
+ // survives next to the reply/error. MUST run AFTER the resolved bubble has been
1463
+ // made non-pending and BEFORE promoteNext*() (so a freshly-promoted Thinking,
1464
+ // which is added only once no pending assistant remains, is preserved).
1465
+ _removeStrayPendingAssistants() {
1466
+ for (var k = this.state.messages.length - 1; k >= 0; k--) {
1467
+ var m = this.state.messages[k];
1468
+ if (m.isPending && m.role === "assistant" && !m.isBackgroundTask) this.state.messages.splice(k, 1);
1469
+ }
1470
+ }
1471
+ // Drop the pending flags on the resolved turn's USER bubble (preserving its
1472
+ // content + background-task marker). Needed because a bg "Indexing:" turn's user
1473
+ // bubble carries isPendingInProcess; leaving it set keeps the bubble visually
1474
+ // stuck and keeps its bgTaskQueue entry alive forever.
1475
+ _clearPendingUserBubble(itemId) {
1476
+ var uIdx = this.state.messages.findIndex(function(m) {
1477
+ return m.role === "user" && m._serverItemId === itemId && (m.isPendingInProcess || m.isPendingQueued || m.isSendingToServer);
1478
+ });
1479
+ if (uIdx === -1) return;
1480
+ var u = this.state.messages[uIdx];
1481
+ var cleaned = { role: "user", content: u.content, _serverItemId: itemId };
1482
+ if (u.isBackgroundTask) cleaned.isBackgroundTask = true;
1483
+ this.state.messages[uIdx] = cleaned;
1484
+ }
1450
1485
  // If an immediate-send request for the current cache key is still in flight
1451
1486
  // (e.g. the view unmounted then remounted mid-request), show the sending
1452
1487
  // state, await it, then render the reply from the cache. Skipped when the
@@ -1485,6 +1520,7 @@ ${options.inlineContentPlaceholder}
1485
1520
  return m.isPending && m._serverItemId === itemId;
1486
1521
  });
1487
1522
  if (idx !== -1) {
1523
+ this._clearPendingUserBubble(itemId);
1488
1524
  if (isErr) {
1489
1525
  this.state.messages[idx] = { role: "assistant", content: answer, isError: true, _serverItemId: itemId };
1490
1526
  this.host.notify();
@@ -1634,12 +1670,16 @@ ${options.inlineContentPlaceholder}
1634
1670
  self.state.messages.forEach(function(m) {
1635
1671
  if (m.isCancelled && m._serverItemId) locallyCancelled[m._serverItemId] = 1;
1636
1672
  });
1673
+ var mappedHasPendingAssistant = mapped.some(function(m) {
1674
+ return m.isPending && m.role === "assistant" && !m.isBackgroundTask;
1675
+ });
1637
1676
  var rescued = [];
1638
1677
  for (var ri = 0; ri < self.state.messages.length; ri++) {
1639
1678
  var mm = self.state.messages[ri];
1640
1679
  if (mm.isBackgroundTask) continue;
1641
1680
  if (mm._serverItemId && serverIds[mm._serverItemId]) continue;
1642
1681
  if (!mm._serverItemId) {
1682
+ if (mappedHasPendingAssistant) continue;
1643
1683
  if (mm.isSendingToServer || mm.isPendingQueued || mm.isPendingInProcess || mm.isPending) rescued.push(mm);
1644
1684
  else if (self.state.sending && mm.role === "user") {
1645
1685
  var next = self.state.messages[ri + 1];
package/dist/engine.cjs CHANGED
@@ -1248,6 +1248,7 @@ var ChatSession = class {
1248
1248
  this.enqueueTypewrite(aiIdx, answer, lid);
1249
1249
  }
1250
1250
  }
1251
+ this._removeStrayPendingAssistants();
1251
1252
  this.promoteNextQueuedToRunning();
1252
1253
  this.updateHistoryCache();
1253
1254
  this.host.notify();
@@ -1290,6 +1291,7 @@ var ChatSession = class {
1290
1291
  if (thPos2 !== -1) this.state.messages.splice(thPos2, 1);
1291
1292
  }
1292
1293
  if (serverId) this.cancelledServerIds.delete(serverId);
1294
+ this._removeStrayPendingAssistants();
1293
1295
  this.promoteNextQueuedToRunning();
1294
1296
  this.updateHistoryCache();
1295
1297
  this.host.notify();
@@ -1303,6 +1305,7 @@ var ChatSession = class {
1303
1305
  return;
1304
1306
  }
1305
1307
  this.insertAtTarget({ role: "assistant", content: getErrorMessage(err), isError: true }, targetIdx);
1308
+ this._removeStrayPendingAssistants();
1306
1309
  this.promoteNextQueuedToRunning();
1307
1310
  this.updateHistoryCache();
1308
1311
  this.host.notify();
@@ -1464,16 +1467,48 @@ var ChatSession = class {
1464
1467
  if (pendingIdx === -1) return Promise.resolve();
1465
1468
  if (latest.isError || !latest.content) {
1466
1469
  this.state.messages[pendingIdx] = { role: "assistant", content: latest.content || "", isError: !!latest.isError };
1470
+ this._removeStrayPendingAssistants();
1467
1471
  this.host.notify();
1468
1472
  this.promoteNextQueuedToRunning();
1469
1473
  return Promise.resolve();
1470
1474
  }
1471
1475
  var lid = this._newLocalId();
1472
1476
  this.state.messages[pendingIdx] = { role: "assistant", content: "", isPending: false, _localId: lid };
1477
+ this._removeStrayPendingAssistants();
1473
1478
  this.host.notify();
1474
1479
  this.promoteNextQueuedToRunning();
1475
1480
  return this.enqueueTypewrite(pendingIdx, latest.content, lid);
1476
1481
  }
1482
+ // Remove any leftover non-background pending ("Thinking…") assistant bubbles.
1483
+ // There is normally at most ONE such bubble at a time (promoteNext* refuses to
1484
+ // add a second), so any extra is a duplicate — it appears when a concurrent
1485
+ // history refetch re-maps the still-"running" turn into a pending placeholder
1486
+ // (with a real _serverItemId) while the local pending bubble (no _serverItemId)
1487
+ // is rescued and re-appended (see loadHistory rescue below). Each resolve path
1488
+ // only replaces the FIRST pending bubble, so without this a stray "Thinking…"
1489
+ // survives next to the reply/error. MUST run AFTER the resolved bubble has been
1490
+ // made non-pending and BEFORE promoteNext*() (so a freshly-promoted Thinking,
1491
+ // which is added only once no pending assistant remains, is preserved).
1492
+ _removeStrayPendingAssistants() {
1493
+ for (var k = this.state.messages.length - 1; k >= 0; k--) {
1494
+ var m = this.state.messages[k];
1495
+ if (m.isPending && m.role === "assistant" && !m.isBackgroundTask) this.state.messages.splice(k, 1);
1496
+ }
1497
+ }
1498
+ // Drop the pending flags on the resolved turn's USER bubble (preserving its
1499
+ // content + background-task marker). Needed because a bg "Indexing:" turn's user
1500
+ // bubble carries isPendingInProcess; leaving it set keeps the bubble visually
1501
+ // stuck and keeps its bgTaskQueue entry alive forever.
1502
+ _clearPendingUserBubble(itemId) {
1503
+ var uIdx = this.state.messages.findIndex(function(m) {
1504
+ return m.role === "user" && m._serverItemId === itemId && (m.isPendingInProcess || m.isPendingQueued || m.isSendingToServer);
1505
+ });
1506
+ if (uIdx === -1) return;
1507
+ var u = this.state.messages[uIdx];
1508
+ var cleaned = { role: "user", content: u.content, _serverItemId: itemId };
1509
+ if (u.isBackgroundTask) cleaned.isBackgroundTask = true;
1510
+ this.state.messages[uIdx] = cleaned;
1511
+ }
1477
1512
  // If an immediate-send request for the current cache key is still in flight
1478
1513
  // (e.g. the view unmounted then remounted mid-request), show the sending
1479
1514
  // state, await it, then render the reply from the cache. Skipped when the
@@ -1512,6 +1547,7 @@ var ChatSession = class {
1512
1547
  return m.isPending && m._serverItemId === itemId;
1513
1548
  });
1514
1549
  if (idx !== -1) {
1550
+ this._clearPendingUserBubble(itemId);
1515
1551
  if (isErr) {
1516
1552
  this.state.messages[idx] = { role: "assistant", content: answer, isError: true, _serverItemId: itemId };
1517
1553
  this.host.notify();
@@ -1661,12 +1697,16 @@ var ChatSession = class {
1661
1697
  self.state.messages.forEach(function(m) {
1662
1698
  if (m.isCancelled && m._serverItemId) locallyCancelled[m._serverItemId] = 1;
1663
1699
  });
1700
+ var mappedHasPendingAssistant = mapped.some(function(m) {
1701
+ return m.isPending && m.role === "assistant" && !m.isBackgroundTask;
1702
+ });
1664
1703
  var rescued = [];
1665
1704
  for (var ri = 0; ri < self.state.messages.length; ri++) {
1666
1705
  var mm = self.state.messages[ri];
1667
1706
  if (mm.isBackgroundTask) continue;
1668
1707
  if (mm._serverItemId && serverIds[mm._serverItemId]) continue;
1669
1708
  if (!mm._serverItemId) {
1709
+ if (mappedHasPendingAssistant) continue;
1670
1710
  if (mm.isSendingToServer || mm.isPendingQueued || mm.isPendingInProcess || mm.isPending) rescued.push(mm);
1671
1711
  else if (self.state.sending && mm.role === "user") {
1672
1712
  var next = self.state.messages[ri + 1];