@bivy/bivy 0.17.3-staging.6 → 0.17.3-staging.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.
@@ -532,6 +532,9 @@ function loadClaudeTranscript(sessionId) {
532
532
  // re-drive, or a session the user resumed), so it's noise — we keep only a
533
533
  // trailing one, where the session actually ended interrupted. See below.
534
534
  const restartNoticeIdx = [];
535
+ // The latest user prompt, and the index of an API-error reply just kept.
536
+ let lastPrompt;
537
+ let lastErrorIdx = -1;
535
538
  try {
536
539
  for (const line of fs.readFileSync(file, "utf8").split(/\r?\n/)) {
537
540
  if (!line.trim())
@@ -567,7 +570,21 @@ function loadClaudeTranscript(sessionId) {
567
570
  continue;
568
571
  if (typeof content !== "string" && !Array.isArray(content))
569
572
  continue;
573
+ // A re-driven prompt: an API-error reply (e.g. a 401 before a credential
574
+ // refresh) followed — past any restart notice — by the same prompt again.
575
+ // The turn was recovered, so drop both; otherwise history shows the error and
576
+ // repeats the prompt at the bottom of the chat.
577
+ if (role === "user" && lastErrorIdx >= 0 && JSON.stringify(content) === JSON.stringify(lastPrompt)) {
578
+ messages.splice(lastErrorIdx, 1);
579
+ restartNoticeIdx.forEach((idx, i) => { if (idx > lastErrorIdx)
580
+ restartNoticeIdx[i] = idx - 1; });
581
+ lastErrorIdx = -1;
582
+ continue;
583
+ }
584
+ if (role === "user" && !(Array.isArray(content) && content.some((block) => block?.type === "tool_result")))
585
+ lastPrompt = content;
570
586
  messages.push({ role, content, timestamp: new Date(entry?.timestamp ?? entry?.createdAt ?? Date.now()).getTime() });
587
+ lastErrorIdx = entry?.isApiErrorMessage === true ? messages.length - 1 : -1;
571
588
  }
572
589
  }
573
590
  catch {
@@ -33,15 +33,30 @@ const selector=el=>{
33
33
  return parts.join(' > ')||el.localName;
34
34
  };
35
35
  let box=null,target=null,onPicked=post,host=null;
36
- const stop=()=>{box?.remove();box=null;target=null;removeEventListener('pointermove',hover,true);removeEventListener('click',pick,true);removeEventListener('keydown',key,true);};
36
+ // Picks on pointerup: iOS never fires click for a tap on a non-clickable
37
+ // element when only window listens, so click would miss most of the page.
38
+ // While pointing, the app sees none of the gesture, including the click after.
39
+ const SWALLOW=['pointerdown','mousedown','mouseup','touchend','click','dblclick','contextmenu'];
40
+ const swallow=e=>{e.preventDefault();e.stopPropagation();};
41
+ // A second listener, so stopping (which the shell does right after a pick)
42
+ // doesn't also drop the guard on the rest of the tap. The tap's click ends it.
43
+ const aftermath=e=>{swallow(e);if(e.type==='click')listen(false,aftermath);};
44
+ const listen=(on,handler=swallow)=>{
45
+ const method=on?addEventListener:removeEventListener;
46
+ if(handler===swallow){method('pointermove',hover,true);method('pointerup',pick,true);method('keydown',key,true);}
47
+ for(const type of SWALLOW)method(type,handler,true);
48
+ };
49
+ const stop=()=>{box?.remove();box=null;target=null;listen(false);};
37
50
  const hover=e=>{
38
51
  const el=document.elementFromPoint(e.clientX,e.clientY);
39
52
  if(!el||el===box||el===host)return;target=el;const r=el.getBoundingClientRect();
40
53
  Object.assign(box.style,{left:r.left+'px',top:r.top+'px',width:r.width+'px',height:r.height+'px'});
41
54
  };
42
55
  const pick=e=>{
43
- e.preventDefault();e.stopPropagation();
56
+ swallow(e);
44
57
  const el=document.elementFromPoint(e.clientX,e.clientY)||target;stop();
58
+ // Keep eating the click (and iOS's delayed mouse events) this tap still fires.
59
+ listen(true,aftermath);setTimeout(()=>listen(false,aftermath),500);
45
60
  if(!el||el===host){onPicked({type:'picked',cancelled:true});return;}const r=el.getBoundingClientRect();
46
61
  onPicked({type:'picked',selector:selector(el),tag:el.localName,text:(el.innerText||el.getAttribute('aria-label')||el.getAttribute('alt')||'').trim().replace(/\\s+/g,' ').slice(0,200),
47
62
  rect:{x:Math.round(r.left),y:Math.round(r.top),width:Math.round(r.width),height:Math.round(r.height)},
@@ -54,7 +69,7 @@ function startPointing(){
54
69
  box.setAttribute('aria-hidden','true');
55
70
  Object.assign(box.style,{position:'fixed',zIndex:2147483647,pointerEvents:'none',outline:'2px solid Highlight',outlineOffset:'2px',borderRadius:'4px',transition:'all 60ms ease-out'});
56
71
  document.documentElement.append(box);
57
- addEventListener('pointermove',hover,true);addEventListener('click',pick,true);addEventListener('keydown',key,true);
72
+ listen(true);
58
73
  }
59
74
  addEventListener('message',e=>{
60
75
  if(!framed||e.origin!==SHELL||e.source!==parent||e.data?.type!=='bivy:point')return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.17.3-staging.6",
3
+ "version": "0.17.3-staging.8",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Run coding agents on machines you own. Open-source, self-hostable agent workspace.",