codeam-cli 2.76.34 → 2.76.36

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 (3) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/index.js +801 -486
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3458,6 +3458,724 @@ CRITICAL \u2014 setup_commands:
3458
3458
  OUTPUT JSON ONLY. NO MARKDOWN. NO COMMENTARY.
3459
3459
  `.trim();
3460
3460
 
3461
+ // ../../packages/shared/src/point-picker/script.ts
3462
+ var POINT_TEXT_MAX = 120;
3463
+ var POINT_HTML_MAX = 600;
3464
+ var DEFAULT_ACCENT = "#7C5CFF";
3465
+ var HEX = /^#[0-9A-Fa-f]{6}$/;
3466
+ var IDENT = /^[A-Za-z_$][\w$]*$/;
3467
+ function js(v) {
3468
+ return JSON.stringify(v).replace(/</g, "\\u003c");
3469
+ }
3470
+ function pointPickerScript(opts) {
3471
+ const multi = opts.mode === "multi";
3472
+ const maxMarks = opts.maxMarks ?? 4;
3473
+ const accent = opts.accent ?? DEFAULT_ACCENT;
3474
+ if (opts.channelField !== "channel" && opts.channelField !== "source") {
3475
+ throw new Error(`pointPickerScript: bad channelField ${String(opts.channelField)}`);
3476
+ }
3477
+ if (!HEX.test(accent))
3478
+ throw new Error(`pointPickerScript: accent must be #RRGGBB, got ${accent}`);
3479
+ if (!Number.isInteger(maxMarks) || maxMarks < 1) {
3480
+ throw new Error(`pointPickerScript: maxMarks must be a positive integer, got ${maxMarks}`);
3481
+ }
3482
+ if (opts.globalName !== void 0 && !IDENT.test(opts.globalName)) {
3483
+ throw new Error(`pointPickerScript: globalName must be an identifier, got ${opts.globalName}`);
3484
+ }
3485
+ return `(function () {
3486
+ 'use strict';
3487
+ var W = window, D = document;
3488
+ var CH = ${js(opts.channel)};
3489
+ var FIELD = ${js(opts.channelField)};
3490
+ var ALLOWED = ${js(opts.allowedParents)};
3491
+ var MULTI = ${multi}, MAX_MARKS = ${maxMarks}, AGENT = ${!!opts.agentHighlights}, REACT_SOURCE = ${!!opts.reactSource};
3492
+ var GLOBAL = ${js(opts.globalName ?? null)};
3493
+ var DEFAULT_ACCENT = ${js(accent)};
3494
+ var GUARD = '__pointPicker:' + CH;
3495
+ if (W[GUARD]) return;
3496
+ try { Object.defineProperty(W, GUARD, { value: true }); } catch (e) { return; }
3497
+ try {
3498
+ var TEXT_MAX = ${POINT_TEXT_MAX}, HTML_MAX = ${POINT_HTML_MAX}, ATTR_MAX = 60;
3499
+ var ACCENT_RE = /^#[0-9A-Fa-f]{6}$/;
3500
+ var AGENT_ACCENT = '#22d3ee';
3501
+ var EASE = 'cubic-bezier(.2,.8,.2,1)';
3502
+ var CHIP_FONT = 'italic 500 13px/1.3 Georgia,"Times New Roman",serif';
3503
+ var CONTROLS = { BUTTON: 1, A: 1, INPUT: 1, SELECT: 1, TEXTAREA: 1, LABEL: 1, SUMMARY: 1 };
3504
+ var ROLES = { button: 1, link: 1, tab: 1, checkbox: 1, switch: 1, menuitem: 1, option: 1, radio: 1, slider: 1 };
3505
+ var VOID = { AREA: 1, BR: 1, COL: 1, EMBED: 1, HR: 1, IMG: 1, INPUT: 1, LINK: 1, META: 1, SOURCE: 1, TRACK: 1, WBR: 1 };
3506
+ var GRAIN = "url(\\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .08 0 0 0 0 .08 0 0 0 0 .09 0 0 0 .12 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E\\")";
3507
+
3508
+ var on = false, driver = null, accent = DEFAULT_ACCENT;
3509
+ var host = null, veil = null, ring = null, chip = null, style = null, leaving = null;
3510
+ var current = null, down = null, tapped = false, bound = [], swallow = null;
3511
+ // Multi mode: the pinned marks, drawn in the page and anchored to the document.
3512
+ // Mark ids carry a per-load prefix: after a reload \`seq\` restarts, and the
3513
+ // app may still hold the previous page's p\u2026-1.
3514
+ var picked = [], seq = 0, marksHost = null, marksRoot = null;
3515
+ var LOAD = (Math.random().toString(36).slice(2) + '0000').slice(0, 4);
3516
+ // The agent's highlights: separate from the person's marks, never numbered.
3517
+ var agentMarks = [], agentHost = null, agentRoot = null, agentLayer = null, agentCursor = null, agentListening = false;
3518
+
3519
+ // Inside a React Native WebView the document is TOP-LEVEL (window.parent ===
3520
+ // window), so the only way out is the bridge RN injects. It carries STRINGS.
3521
+ function rnBridge() {
3522
+ if (!GLOBAL) return null;
3523
+ var rn = W.ReactNativeWebView;
3524
+ return rn && typeof rn.postMessage === 'function' ? rn : null;
3525
+ }
3526
+
3527
+ function send(type, extra) {
3528
+ if (!driver) return;
3529
+ var msg = {};
3530
+ msg[FIELD] = CH;
3531
+ msg.type = type;
3532
+ if (extra) for (var k in extra) msg[k] = extra[k];
3533
+ var rn = rnBridge();
3534
+ if (rn) {
3535
+ try { rn.postMessage(JSON.stringify(msg)); } catch (e) {}
3536
+ return;
3537
+ }
3538
+ if (W.parent === W) return;
3539
+ try { W.parent.postMessage(msg, driver); } catch (e) {}
3540
+ }
3541
+
3542
+ function clip(s, max) { return s.length > max ? s.slice(0, max - 1) + '\\u2026' : s; }
3543
+ function squash(s) { return String(s == null ? '' : s).replace(/\\s+/g, ' ').trim(); }
3544
+ function attr(el, n) { var v = el.getAttribute ? el.getAttribute(n) : null; return v == null ? '' : String(v); }
3545
+ function tagOf(el) { return String(el.tagName || '').toLowerCase(); }
3546
+ function reduced() {
3547
+ try { return !!(W.matchMedia && W.matchMedia('(prefers-reduced-motion: reduce)').matches); } catch (e) { return false; }
3548
+ }
3549
+ function drop(node) { if (node && node.parentNode) node.parentNode.removeChild(node); }
3550
+ function shadowOf(el) { return el.attachShadow ? el.attachShadow({ mode: 'closed' }) : el; }
3551
+ function clear(node) { while (node && node.childNodes && node.childNodes.length) node.removeChild(node.childNodes[0]); }
3552
+
3553
+ // A tap lands on the innermost node (the <span> in a button, the <path> in
3554
+ // an icon); the person means the control around it.
3555
+ function target(el) {
3556
+ var n = el;
3557
+ for (var depth = 0; n && n !== D.body && n !== D.documentElement && depth < 6; depth++) {
3558
+ if (CONTROLS[String(n.tagName).toUpperCase()] || ROLES[attr(n, 'role')]) return n;
3559
+ n = n.parentElement;
3560
+ }
3561
+ n = el;
3562
+ while (n.ownerSVGElement) n = n.ownerSVGElement;
3563
+ return n;
3564
+ }
3565
+
3566
+ function pickable(el) {
3567
+ return !!el && el.nodeType === 1 && el !== D.documentElement && el !== D.body &&
3568
+ el !== host && el !== marksHost && el !== agentHost;
3569
+ }
3570
+
3571
+ // CSS.escape where the browser has it; else the same algorithm (CSSOM).
3572
+ // Without it a Tailwind class (\`md:flex\`, \`w-1/2\`) makes an invalid selector.
3573
+ function cssEsc(v) {
3574
+ v = String(v);
3575
+ try { if (W.CSS && typeof W.CSS.escape === 'function') return W.CSS.escape(v); } catch (e) {}
3576
+ var out = '';
3577
+ for (var i = 0; i < v.length; i++) {
3578
+ var ch = v.charAt(i), code = v.charCodeAt(i);
3579
+ if (code === 0) out += '\\uFFFD';
3580
+ else if ((code >= 1 && code <= 31) || code === 127 || (i === 0 && code >= 48 && code <= 57) ||
3581
+ (i === 1 && code >= 48 && code <= 57 && v.charAt(0) === '-')) out += '\\\\' + code.toString(16) + ' ';
3582
+ else if (i === 0 && ch === '-' && v.length === 1) out += '\\\\-';
3583
+ else if (code >= 128 || ch === '-' || ch === '_' || /[0-9A-Za-z]/.test(ch)) out += ch;
3584
+ else out += '\\\\' + ch;
3585
+ }
3586
+ return out;
3587
+ }
3588
+
3589
+ // One step of the path. \`lean\` (tag + position only) is the fallback for
3590
+ // a step that alone would not fit the budget.
3591
+ function step(el, lean) {
3592
+ var t = tagOf(el);
3593
+ if (el.id && !lean) return t + '#' + cssEsc(el.id);
3594
+ var c = lean ? [] : squash(attr(el, 'class')).split(' ').filter(Boolean).slice(0, 2);
3595
+ var s = t;
3596
+ for (var k = 0; k < c.length; k++) s += '.' + cssEsc(c[k]);
3597
+ var p = el.parentElement;
3598
+ if (p && p.children) {
3599
+ var same = 0, index = 0;
3600
+ for (var i = 0; i < p.children.length; i++) {
3601
+ if (p.children[i].tagName === el.tagName) {
3602
+ same++;
3603
+ if (p.children[i] === el) index = same;
3604
+ }
3605
+ }
3606
+ if (same > 1) s += ':nth-of-type(' + index + ')';
3607
+ }
3608
+ return s;
3609
+ }
3610
+
3611
+ // Never cut with an ellipsis (that would be no selector at all): drop the
3612
+ // outermost ancestors until the path fits, so it always matches the element.
3613
+ function selectorFor(el) {
3614
+ var parts = [], n = el;
3615
+ while (n && n.nodeType === 1 && n !== D.body && n !== D.documentElement && parts.length < 4) {
3616
+ parts.unshift(step(n));
3617
+ if (n.id) break;
3618
+ n = n.parentElement;
3619
+ }
3620
+ while (parts.length > 1 && parts.join(' > ').length > 200) parts.shift();
3621
+ if (parts[0].length > 200) parts[0] = step(el, true);
3622
+ return parts.join(' > ');
3623
+ }
3624
+
3625
+ function textOf(el) {
3626
+ return clip(squash(attr(el, 'aria-label') || el.textContent || attr(el, 'placeholder') || attr(el, 'alt') || attr(el, 'title')), TEXT_MAX);
3627
+ }
3628
+
3629
+ function esc(s) { return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;'); }
3630
+
3631
+ // The element's markup within a budget: attributes trimmed, never more than
3632
+ // HTML_MAX characters walked, so a huge container costs nothing.
3633
+ function htmlOf(el) {
3634
+ var out = [], left = HTML_MAX + 1;
3635
+ function put(s) {
3636
+ if (left <= 0) return;
3637
+ out.push(s.length > left ? s.slice(0, left) : s);
3638
+ left -= s.length;
3639
+ }
3640
+ function walk(n, depth) {
3641
+ if (left <= 0) return;
3642
+ if (n.nodeType === 3) {
3643
+ var text = String(n.nodeValue == null ? '' : n.nodeValue).replace(/\\s+/g, ' ');
3644
+ if (text.trim()) put(esc(text));
3645
+ return;
3646
+ }
3647
+ if (n.nodeType !== 1) return;
3648
+ var t = tagOf(n);
3649
+ put('<' + t);
3650
+ var a = n.attributes || [];
3651
+ for (var i = 0; i < a.length; i++) put(' ' + a[i].name + '="' + esc(clip(String(a[i].value), ATTR_MAX)) + '"');
3652
+ put('>');
3653
+ if (VOID[t.toUpperCase()]) return;
3654
+ if (t === 'script' || t === 'style') put('\\u2026');
3655
+ else if (depth >= 4) { if (n.childNodes && n.childNodes.length) put('\\u2026'); }
3656
+ else for (var c = 0; n.childNodes && c < n.childNodes.length && left > 0; c++) walk(n.childNodes[c], depth + 1);
3657
+ put('</' + t + '>');
3658
+ }
3659
+ walk(el, 0);
3660
+ var s = out.join('');
3661
+ return s.length > HTML_MAX ? s.slice(0, HTML_MAX - 1) + '\\u2026' : s;
3662
+ }
3663
+
3664
+ // The React component's file and line, when a dev build exposes them: the
3665
+ // agent gets "components/buy-button.tsx:42" instead of a CSS path.
3666
+ function reactSourceOf(el) {
3667
+ try {
3668
+ for (var key in el) {
3669
+ if (key.indexOf('__reactFiber$') !== 0) continue;
3670
+ var fiber = el[key];
3671
+ while (fiber) {
3672
+ var src = fiber._debugSource;
3673
+ if (src && src.fileName) return { file: String(src.fileName), line: Number(src.lineNumber) || null };
3674
+ fiber = fiber.return;
3675
+ }
3676
+ }
3677
+ } catch (e) {}
3678
+ return null;
3679
+ }
3680
+
3681
+ function describe(el) {
3682
+ var r = el.getBoundingClientRect();
3683
+ var out = {
3684
+ tag: tagOf(el),
3685
+ id: el.id ? clip(String(el.id), 100) : null,
3686
+ classes: clip(squash(attr(el, 'class')), 200),
3687
+ selector: selectorFor(el),
3688
+ text: textOf(el),
3689
+ html: htmlOf(el),
3690
+ rect: { x: Math.round(r.left), y: Math.round(r.top), w: Math.round(r.width), h: Math.round(r.height) }
3691
+ };
3692
+ if (MULTI) out.viewport = { w: W.innerWidth, h: W.innerHeight };
3693
+ if (REACT_SOURCE) out.source = reactSourceOf(el);
3694
+ return out;
3695
+ }
3696
+
3697
+ function kindOf(el) {
3698
+ var t = tagOf(el), role = attr(el, 'role'), type = attr(el, 'type');
3699
+ if (t === 'button' || role === 'button' || (t === 'input' && /^(button|submit|reset)$/.test(type))) return 'button';
3700
+ if (t === 'a' || role === 'link') return 'link';
3701
+ if (t === 'input' || t === 'textarea' || t === 'select') return 'field';
3702
+ if (t === 'img' || t === 'svg' || t === 'picture' || t === 'canvas') return 'image';
3703
+ if (/^h[1-6]$/.test(t)) return 'heading';
3704
+ return t;
3705
+ }
3706
+
3707
+ function label(el) {
3708
+ var text = squash(textOf(el));
3709
+ var kind = kindOf(el);
3710
+ return text ? '\\u201c' + clip(text, 28) + '\\u201d ' + kind : kind;
3711
+ }
3712
+
3713
+ function cursor() {
3714
+ var svg = "<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32'><circle cx='16' cy='16' r='9.5' fill='" + accent + "' fill-opacity='.14' stroke='" + accent + "' stroke-width='2'/><path d='M16 1v7M16 24v7M1 16h7M24 16h7' stroke='" + accent + "' stroke-width='1.6' stroke-linecap='round'/><circle cx='16' cy='16' r='1.6' fill='" + accent + "'/></svg>";
3715
+ return 'url("data:image/svg+xml,' + encodeURIComponent(svg) + '") 16 16, crosshair';
3716
+ }
3717
+
3718
+ function canHole() {
3719
+ try { return !!(W.CSS && W.CSS.supports && W.CSS.supports('clip-path', 'path(evenodd, "M0 0H1V1Z")')); } catch (e) { return false; }
3720
+ }
3721
+
3722
+ // The veil covers the app except a rounded window around the element.
3723
+ function holePath(r) {
3724
+ var w = Math.round(W.innerWidth), h = Math.round(W.innerHeight), p = 6;
3725
+ var x = Math.round(r.left - p), y = Math.round(r.top - p);
3726
+ var rw = Math.round(r.width + 2 * p), rh = Math.round(r.height + 2 * p);
3727
+ var k = Math.max(0, Math.min(12, Math.floor(rw / 2), Math.floor(rh / 2)));
3728
+ var arc = 'A' + k + ' ' + k + ' 0 0 1 ';
3729
+ return 'path(evenodd, "M0 0H' + w + 'V' + h + 'H0Z' +
3730
+ 'M' + (x + k) + ' ' + y + 'H' + (x + rw - k) + arc + (x + rw) + ' ' + (y + k) +
3731
+ 'V' + (y + rh - k) + arc + (x + rw - k) + ' ' + (y + rh) +
3732
+ 'H' + (x + k) + arc + x + ' ' + (y + rh - k) +
3733
+ 'V' + (y + k) + arc + (x + k) + ' ' + y + 'Z")';
3734
+ }
3735
+
3736
+ // The Remix ring and chip, shared by the pointer, the marks and the agent.
3737
+ function ringCss(color) {
3738
+ return 'box-sizing:border-box;border:2px solid ' + color + ';border-radius:12px;box-shadow:0 0 0 5px ' + color + '29';
3739
+ }
3740
+ function chipCss(color) {
3741
+ return 'white-space:nowrap;padding:4px 10px;border-radius:999px;background:#141416;color:#f6f3ee;font:' + CHIP_FONT +
3742
+ ';box-shadow:0 0 0 1px ' + color;
3743
+ }
3744
+
3745
+ // A host for our nodes: a custom element, max z-index, its own closed
3746
+ // shadow root so the page's CSS can't restyle (or hide) what we draw. Last
3747
+ // child of <html>, so at an equal z-index it still paints on top.
3748
+ function makeHost(role, position) {
3749
+ var el = D.createElement('remix-point');
3750
+ if (role === 'point') el.setAttribute('data-remix-point', '');
3751
+ el.setAttribute('data-codeam', role);
3752
+ el.style.cssText = 'all:initial;position:' + position + ';top:0;left:0;width:0;height:0;pointer-events:none;z-index:2147483647';
3753
+ return el;
3754
+ }
3755
+
3756
+ function build() {
3757
+ if (leaving && leaving.parentNode) leaving.parentNode.removeChild(leaving);
3758
+ leaving = null;
3759
+ var motion = reduced() ? 'none' : 'transform .16s ' + EASE + ', width .16s ' + EASE + ', height .16s ' + EASE + ', opacity .16s';
3760
+ host = makeHost('point', 'fixed');
3761
+ var root = shadowOf(host);
3762
+ veil = D.createElement('div');
3763
+ veil.setAttribute('data-codeam', 'veil');
3764
+ veil.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;pointer-events:none;opacity:0;' +
3765
+ 'background:rgba(246,243,238,.46) ' + GRAIN + ';-webkit-backdrop-filter:grayscale(.7);backdrop-filter:grayscale(.7);' +
3766
+ 'transition:' + (reduced() ? 'none' : 'opacity .2s');
3767
+ ring = D.createElement('div');
3768
+ ring.setAttribute('data-codeam', 'ring');
3769
+ ring.style.cssText = 'position:fixed;top:0;left:0;width:0;height:0;pointer-events:none;opacity:0;' +
3770
+ ringCss(accent) + ';transition:' + motion;
3771
+ chip = D.createElement('div');
3772
+ chip.setAttribute('data-codeam', 'chip');
3773
+ chip.style.cssText = 'position:fixed;top:0;left:0;pointer-events:none;opacity:0;' + chipCss(accent) + ';transition:' + motion;
3774
+ root.appendChild(veil);
3775
+ root.appendChild(ring);
3776
+ root.appendChild(chip);
3777
+ D.documentElement.appendChild(host);
3778
+ style = D.createElement('style');
3779
+ style.textContent = 'html[data-remix-pointing],html[data-remix-pointing] *{cursor:' + cursor() + ' !important;' +
3780
+ 'user-select:none !important;-webkit-user-select:none !important;-webkit-touch-callout:none !important}';
3781
+ (D.head || D.documentElement).appendChild(style);
3782
+ D.documentElement.setAttribute('data-remix-pointing', '');
3783
+ }
3784
+
3785
+ function paint(el) {
3786
+ current = el;
3787
+ var r = el.getBoundingClientRect();
3788
+ ring.style.transform = 'translate(' + Math.round(r.left - 4) + 'px,' + Math.round(r.top - 4) + 'px)';
3789
+ ring.style.width = Math.round(r.width + 8) + 'px';
3790
+ ring.style.height = Math.round(r.height + 8) + 'px';
3791
+ ring.style.opacity = '1';
3792
+ if (canHole()) {
3793
+ veil.style.clipPath = holePath(r);
3794
+ veil.style.opacity = '1';
3795
+ }
3796
+ chip.textContent = label(el);
3797
+ var top = r.top >= 34 ? r.top - 32 : r.top + r.height + 8;
3798
+ var left = Math.max(8, Math.min(r.left, W.innerWidth - 200));
3799
+ chip.style.transform = 'translate(' + Math.round(left) + 'px,' + Math.round(top) + 'px)';
3800
+ chip.style.opacity = '1';
3801
+ }
3802
+
3803
+ // Paint, and in multi mode tell the parent what is under the finger.
3804
+ function hoverTo(el) {
3805
+ var changed = el !== current;
3806
+ paint(el);
3807
+ if (changed && MULTI) send('hover', { element: describe(el) });
3808
+ }
3809
+
3810
+ // While pointing, the app never sees the gesture: no click, no focus, no
3811
+ // drag. Touch scrolling still works (its default is left alone).
3812
+ function block(e) {
3813
+ if (e.stopImmediatePropagation) e.stopImmediatePropagation();
3814
+ if (e.cancelable && e.type !== 'touchstart' && e.type !== 'touchmove') e.preventDefault();
3815
+ }
3816
+
3817
+ // What is under the pointer. Hit-testing first (a touch keeps targeting
3818
+ // the element it went down on); the event target where there is none.
3819
+ function at(e) {
3820
+ var el = null;
3821
+ if (D.elementFromPoint) {
3822
+ try { el = D.elementFromPoint(e.clientX, e.clientY); } catch (x) { el = null; }
3823
+ }
3824
+ if (!el && e.target && e.target.nodeType === 1) el = e.target;
3825
+ return pickable(el) ? target(el) : null;
3826
+ }
3827
+
3828
+ function onDown(e) {
3829
+ block(e);
3830
+ down = { id: e.pointerId };
3831
+ tapped = true;
3832
+ var el = at(e);
3833
+ if (el) hoverTo(el);
3834
+ }
3835
+
3836
+ function onMove(e) {
3837
+ block(e);
3838
+ var el = at(e);
3839
+ if (el && el !== current) hoverTo(el);
3840
+ }
3841
+
3842
+ // Browsers without pointer events (old WebViews) only send mouse events.
3843
+ // Never blocked: the app keeps its own mousemove, as it always did.
3844
+ function onMouseMove(e) {
3845
+ var el = at(e);
3846
+ if (el && el !== current) hoverTo(el);
3847
+ }
3848
+
3849
+ function onUp(e) {
3850
+ block(e);
3851
+ var d = down;
3852
+ down = null;
3853
+ if (!d || d.id !== e.pointerId || e.button > 0) return;
3854
+ var el = at(e) || current;
3855
+ if (el) {
3856
+ pick(el);
3857
+ armClickSwallow();
3858
+ }
3859
+ }
3860
+
3861
+ // The click that follows a tap was already picked on pointerup; a click
3862
+ // with no pointer gesture before it (an old WebView) is the pick itself.
3863
+ function onClick(e) {
3864
+ block(e);
3865
+ if (tapped) { tapped = false; return; }
3866
+ var el = at(e);
3867
+ if (el) pick(el);
3868
+ }
3869
+
3870
+ // The browser fires a click after the pointerup that picked. Once pointing
3871
+ // is off (single mode, or the parent turned it off in between) nothing of
3872
+ // ours would catch it, and a pointed-at link would navigate. One shot:
3873
+ // gone on the next click, or after 400 ms when no click comes.
3874
+ function armClickSwallow() {
3875
+ disarmClickSwallow();
3876
+ var fn = function (e) {
3877
+ block(e);
3878
+ disarmClickSwallow();
3879
+ };
3880
+ swallow = fn;
3881
+ W.addEventListener('click', fn, { capture: true, passive: false });
3882
+ W.setTimeout(function () { if (swallow === fn) disarmClickSwallow(); }, 400);
3883
+ }
3884
+
3885
+ function disarmClickSwallow() {
3886
+ if (!swallow) return;
3887
+ W.removeEventListener('click', swallow, { capture: true });
3888
+ swallow = null;
3889
+ }
3890
+
3891
+ // The browser took the touch over to scroll: that was not a tap.
3892
+ function onCancel(e) {
3893
+ block(e);
3894
+ down = null;
3895
+ tapped = false;
3896
+ }
3897
+
3898
+ function onKey(e) {
3899
+ block(e);
3900
+ // In multi mode the parent owns the toggle: Escape ends nothing it can't see.
3901
+ if (!MULTI && e.type === 'keydown' && (e.key === 'Escape' || e.key === 'Esc')) {
3902
+ send('cancel');
3903
+ off(false);
3904
+ }
3905
+ }
3906
+
3907
+ function onScroll() {
3908
+ if (current) paint(current);
3909
+ if (picked.length) drawMarks();
3910
+ }
3911
+
3912
+ var HANDLERS = [
3913
+ ['pointerdown', onDown], ['pointermove', onMove], ['pointerup', onUp], ['pointercancel', onCancel],
3914
+ ['mousedown', block], ['mouseup', block], ['mousemove', onMouseMove], ['click', onClick], ['dblclick', block], ['auxclick', block],
3915
+ ['contextmenu', block], ['touchstart', block], ['touchmove', block], ['touchend', block],
3916
+ ['submit', block], ['keydown', onKey], ['keyup', block], ['keypress', block]
3917
+ ];
3918
+
3919
+ function pick(el) {
3920
+ var element = describe(el);
3921
+ if (!MULTI) {
3922
+ off(true);
3923
+ send('pick', { element: element });
3924
+ return;
3925
+ }
3926
+ // Past the cap the pick still goes up (the app says "4 marks max") but
3927
+ // leaves nothing in the page the app would never be able to remove.
3928
+ if (picked.length < MAX_MARKS) {
3929
+ var id = 'p' + LOAD + '-' + (++seq);
3930
+ picked.push({ id: id, el: el });
3931
+ element.markId = id;
3932
+ drawMarks(id);
3933
+ }
3934
+ send('pick', { element: element });
3935
+ }
3936
+
3937
+ // The pinned marks: Remix rings with a numbered chip, anchored to the
3938
+ // DOCUMENT (absolute + scroll) so they travel with the content.
3939
+ function drawMarks(fresh) {
3940
+ if (!picked.length) {
3941
+ drop(marksHost);
3942
+ marksHost = marksRoot = null;
3943
+ return;
3944
+ }
3945
+ if (!marksHost || !marksHost.parentNode) {
3946
+ marksHost = makeHost('marks', 'absolute');
3947
+ marksRoot = shadowOf(marksHost);
3948
+ D.documentElement.appendChild(marksHost);
3949
+ }
3950
+ clear(marksRoot);
3951
+ var calm = reduced();
3952
+ if (!calm) {
3953
+ var keys = D.createElement('style');
3954
+ keys.textContent = '@keyframes pp-pin{from{opacity:0;transform:scale(1.04)}to{opacity:1;transform:scale(1)}}';
3955
+ marksRoot.appendChild(keys);
3956
+ }
3957
+ var sx = W.scrollX || W.pageXOffset || 0, sy = W.scrollY || W.pageYOffset || 0;
3958
+ for (var i = 0; i < picked.length; i++) {
3959
+ var r = picked[i].el.getBoundingClientRect();
3960
+ var box = D.createElement('div');
3961
+ box.setAttribute('data-codeam', 'mark');
3962
+ box.style.cssText = 'position:absolute;pointer-events:none;' + ringCss(accent) +
3963
+ ';left:' + Math.round(r.left + sx - 4) + 'px;top:' + Math.round(r.top + sy - 4) + 'px' +
3964
+ ';width:' + Math.round(r.width + 8) + 'px;height:' + Math.round(r.height + 8) + 'px' +
3965
+ (!calm && picked[i].id === fresh ? ';animation:pp-pin .32s ' + EASE : '');
3966
+ var badge = D.createElement('div');
3967
+ badge.setAttribute('data-codeam', 'mark-badge');
3968
+ badge.style.cssText = 'position:absolute;left:-11px;top:-11px;width:22px;height:22px;border-radius:11px;' +
3969
+ 'background:#141416;color:#f6f3ee;font:italic 600 12px/22px Georgia,"Times New Roman",serif;text-align:center;' +
3970
+ 'box-shadow:0 0 0 1px ' + accent + ';pointer-events:none';
3971
+ badge.textContent = String(i + 1);
3972
+ box.appendChild(badge);
3973
+ marksRoot.appendChild(box);
3974
+ }
3975
+ }
3976
+
3977
+ // The parent sends the ids still alive, in order: a removed chip removes
3978
+ // its mark, and the rest renumber (what the person writes, "1 is off",
3979
+ // refers to these numbers).
3980
+ function syncMarks(ids) {
3981
+ var byId = {};
3982
+ for (var i = 0; i < picked.length; i++) byId[picked[i].id] = picked[i];
3983
+ var next = [];
3984
+ for (var j = 0; j < ids.length; j++) if (byId[ids[j]]) next.push(byId[ids[j]]);
3985
+ picked = next;
3986
+ drawMarks();
3987
+ }
3988
+
3989
+ // The agent pointing (highlight_element): a cursor travels to the element,
3990
+ // centres it, and leaves a cyan Remix ring with its label. Independent of
3991
+ // pointing mode, anchored to the document like the marks.
3992
+ function ensureAgent() {
3993
+ if (agentHost && agentHost.parentNode) return;
3994
+ agentHost = makeHost('agent', 'absolute');
3995
+ agentRoot = shadowOf(agentHost);
3996
+ var keys = D.createElement('style');
3997
+ keys.textContent =
3998
+ '@keyframes pp-agent-pulse{0%{box-shadow:0 0 0 0 rgba(34,211,238,.55)}70%{box-shadow:0 0 0 10px rgba(34,211,238,0)}100%{box-shadow:0 0 0 5px rgba(34,211,238,.16)}}' +
3999
+ '@keyframes pp-agent-in{from{opacity:0;transform:scale(1.04)}to{opacity:1;transform:scale(1)}}';
4000
+ agentRoot.appendChild(keys);
4001
+ agentLayer = D.createElement('div');
4002
+ agentRoot.appendChild(agentLayer);
4003
+ D.documentElement.appendChild(agentHost);
4004
+ }
4005
+
4006
+ function ensureAgentCursor() {
4007
+ if (agentCursor && agentCursor.parentNode) return agentCursor;
4008
+ agentCursor = D.createElement('div');
4009
+ agentCursor.setAttribute('data-codeam', 'agent-cursor');
4010
+ agentCursor.style.cssText = 'position:fixed;pointer-events:none;opacity:0;' +
4011
+ 'left:' + Math.round(W.innerWidth / 2) + 'px;top:' + Math.round(W.innerHeight - 40) + 'px;' +
4012
+ 'transition:' + (reduced() ? 'none' : 'left .6s cubic-bezier(.22,1,.36,1),top .6s cubic-bezier(.22,1,.36,1),opacity .3s');
4013
+ agentCursor.innerHTML =
4014
+ '<svg width="22" height="22" viewBox="0 0 24 24" style="display:block;filter:drop-shadow(0 1px 2px rgba(0,0,0,.5))">' +
4015
+ '<path d="M4 2l15 8.5-6.6 1.6L9 18.5z" fill="' + AGENT_ACCENT + '" stroke="#141416" stroke-width="1.2"/></svg>' +
4016
+ '<div style="margin:2px 0 0 14px;display:inline-block;' + chipCss(AGENT_ACCENT) + ';padding:2px 8px;font-size:11px">Agent</div>';
4017
+ agentRoot.appendChild(agentCursor);
4018
+ return agentCursor;
4019
+ }
4020
+
4021
+ function drawAgentMarks() {
4022
+ if (!agentLayer) return;
4023
+ clear(agentLayer);
4024
+ var calm = reduced();
4025
+ var sx = W.scrollX || W.pageXOffset || 0, sy = W.scrollY || W.pageYOffset || 0;
4026
+ for (var i = 0; i < agentMarks.length; i++) {
4027
+ var m = agentMarks[i];
4028
+ if (m.el.isConnected === false) continue;
4029
+ var r = m.el.getBoundingClientRect();
4030
+ var box = D.createElement('div');
4031
+ box.setAttribute('data-codeam', 'agent-mark');
4032
+ box.style.cssText = 'position:absolute;pointer-events:none;' + ringCss(AGENT_ACCENT) +
4033
+ ';left:' + Math.round(r.left + sx - 4) + 'px;top:' + Math.round(r.top + sy - 4) + 'px' +
4034
+ ';width:' + Math.round(r.width + 8) + 'px;height:' + Math.round(r.height + 8) + 'px' +
4035
+ (calm ? '' : ';animation:pp-agent-in .25s ' + EASE + ',pp-agent-pulse 1.6s ease-out 3');
4036
+ if (m.label) {
4037
+ var tag = D.createElement('div');
4038
+ tag.setAttribute('data-codeam', 'agent-mark-label');
4039
+ // Above the element when it fits, else below \u2014 same rule as the chip.
4040
+ tag.style.cssText = 'position:absolute;left:0;' + (r.top >= 34 ? 'top:-30px' : 'top:' + Math.round(r.height + 12) + 'px') +
4041
+ ';pointer-events:none;' + chipCss(AGENT_ACCENT) + ';max-width:260px;overflow:hidden;text-overflow:ellipsis';
4042
+ tag.textContent = m.label;
4043
+ box.appendChild(tag);
4044
+ }
4045
+ agentLayer.appendChild(box);
4046
+ }
4047
+ }
4048
+
4049
+ function listenAgentLayout() {
4050
+ if (agentListening) return;
4051
+ agentListening = true;
4052
+ W.addEventListener('scroll', drawAgentMarks, true);
4053
+ W.addEventListener('resize', drawAgentMarks);
4054
+ }
4055
+
4056
+ function agentHighlight(selector, text) {
4057
+ var el = null;
4058
+ try { el = D.querySelector(String(selector)); } catch (e) { el = null; }
4059
+ if (!el || !el.getBoundingClientRect) return false;
4060
+ ensureAgent();
4061
+ listenAgentLayout();
4062
+ var calm = reduced();
4063
+ try { el.scrollIntoView({ block: 'center', inline: 'nearest', behavior: calm ? 'auto' : 'smooth' }); } catch (e) {}
4064
+ var pointer = ensureAgentCursor();
4065
+ var owner = agentHost;
4066
+ // After the smooth scroll the cursor travels to the element and, on
4067
+ // arrival, the ring appears: read as "the agent is pointing at this".
4068
+ W.setTimeout(function () {
4069
+ if (agentHost !== owner) return;
4070
+ var r = el.getBoundingClientRect();
4071
+ pointer.style.opacity = '1';
4072
+ pointer.style.left = Math.round(r.left + Math.min(r.width / 2, 40)) + 'px';
4073
+ pointer.style.top = Math.round(r.top + Math.min(r.height / 2, 24)) + 'px';
4074
+ W.setTimeout(function () {
4075
+ if (agentHost !== owner) return;
4076
+ agentMarks.push({ el: el, label: text ? String(text).slice(0, 80) : '' });
4077
+ drawAgentMarks();
4078
+ }, 620);
4079
+ }, 380);
4080
+ return true;
4081
+ }
4082
+
4083
+ function agentClear() {
4084
+ agentMarks = [];
4085
+ drop(agentHost);
4086
+ agentHost = agentRoot = agentLayer = agentCursor = null;
4087
+ if (agentListening) {
4088
+ W.removeEventListener('scroll', drawAgentMarks, true);
4089
+ W.removeEventListener('resize', drawAgentMarks);
4090
+ agentListening = false;
4091
+ }
4092
+ }
4093
+
4094
+ function enable(origin, a) {
4095
+ driver = origin;
4096
+ accent = typeof a === 'string' && ACCENT_RE.test(a) ? a : DEFAULT_ACCENT;
4097
+ if (!on) {
4098
+ on = true;
4099
+ build();
4100
+ for (var i = 0; i < HANDLERS.length; i++) {
4101
+ W.addEventListener(HANDLERS[i][0], HANDLERS[i][1], { capture: true, passive: false });
4102
+ bound.push(HANDLERS[i]);
4103
+ }
4104
+ W.addEventListener('scroll', onScroll, true);
4105
+ W.addEventListener('resize', onScroll, true);
4106
+ }
4107
+ send(MULTI ? 'ready' : 'on');
4108
+ }
4109
+
4110
+ function off(pinned) {
4111
+ if (!on) return;
4112
+ on = false;
4113
+ while (bound.length) {
4114
+ var h = bound.pop();
4115
+ W.removeEventListener(h[0], h[1], { capture: true });
4116
+ }
4117
+ W.removeEventListener('scroll', onScroll, true);
4118
+ W.removeEventListener('resize', onScroll, true);
4119
+ D.documentElement.removeAttribute('data-remix-pointing');
4120
+ if (style && style.parentNode) style.parentNode.removeChild(style);
4121
+ var node = host;
4122
+ if (pinned && node && !reduced()) {
4123
+ // A short "pinned" beat before the ring goes, so the tap feels taken.
4124
+ ring.style.transition = 'transform .32s ' + EASE + ', opacity .32s';
4125
+ ring.style.transform += ' scale(1.04)';
4126
+ ring.style.opacity = '0';
4127
+ veil.style.opacity = '0';
4128
+ chip.style.opacity = '0';
4129
+ leaving = node;
4130
+ W.setTimeout(function () {
4131
+ if (node.parentNode) node.parentNode.removeChild(node);
4132
+ if (leaving === node) leaving = null;
4133
+ }, 320);
4134
+ } else if (node && node.parentNode) {
4135
+ node.parentNode.removeChild(node);
4136
+ }
4137
+ host = veil = ring = chip = style = null;
4138
+ current = null;
4139
+ down = null;
4140
+ tapped = false;
4141
+ }
4142
+
4143
+ W.addEventListener('message', function (e) {
4144
+ if (ALLOWED.indexOf(e.origin) === -1 || e.source !== W.parent) return;
4145
+ var d = e.data;
4146
+ if (!d || typeof d !== 'object' || d[FIELD] !== CH) return;
4147
+ if (d.type === 'enable') enable(e.origin, d.accent);
4148
+ else if (d.type === 'disable' && e.origin === driver) off(false);
4149
+ else if (MULTI && d.type === 'marks' && d.ids && d.ids.length !== undefined) syncMarks(d.ids);
4150
+ else if (AGENT && d.type === 'agent-highlight' && d.selector) agentHighlight(d.selector, d.label);
4151
+ else if (AGENT && d.type === 'agent-clear') agentClear();
4152
+ });
4153
+
4154
+ if (!MULTI) {
4155
+ try {
4156
+ Object.defineProperty(W, '__remixPoint', {
4157
+ value: Object.freeze({ version: 1, active: function () { return on; } })
4158
+ });
4159
+ } catch (e) {}
4160
+ }
4161
+
4162
+ // The React Native door. Only where the bridge exists: on a normal page it
4163
+ // would be a global that turns the picker on with nobody's permission.
4164
+ if (GLOBAL && rnBridge()) {
4165
+ W[GLOBAL] = {
4166
+ enable: function () { enable('react-native'); },
4167
+ disable: function () { off(false); },
4168
+ marks: function (ids) { if (MULTI && ids && ids.length !== undefined) syncMarks(ids); },
4169
+ highlight: function (selector, text) { return AGENT ? agentHighlight(selector, text) : false; },
4170
+ clear: function () { if (AGENT) agentClear(); }
4171
+ };
4172
+ }
4173
+ } catch (e) {
4174
+ // Whatever happens, the person's app keeps working.
4175
+ }
4176
+ })();`;
4177
+ }
4178
+
3461
4179
  // src/config.ts
3462
4180
  var fs3 = __toESM(require("fs"));
3463
4181
  var os2 = __toESM(require("os"));
@@ -10636,7 +11354,7 @@ function readAnonId() {
10636
11354
  }
10637
11355
  function superProperties() {
10638
11356
  return {
10639
- cliVersion: true ? "2.76.34" : "0.0.0-dev",
11357
+ cliVersion: true ? "2.76.36" : "0.0.0-dev",
10640
11358
  nodeVersion: process.version,
10641
11359
  platform: process.platform,
10642
11360
  arch: process.arch,
@@ -10885,7 +11603,7 @@ var http = __toESM(require("http"));
10885
11603
  // package.json
10886
11604
  var package_default = {
10887
11605
  name: "codeam-cli",
10888
- version: "2.76.34",
11606
+ version: "2.76.36",
10889
11607
  description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
10890
11608
  type: "commonjs",
10891
11609
  main: "dist/index.js",
@@ -12261,7 +12979,7 @@ var CommandRelayService = class _CommandRelayService {
12261
12979
  // fresh + clear the "CLI update available" banner after a self-update
12262
12980
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
12263
12981
  // pair/reconnect). Older backends ignore the extra field.
12264
- ..."2.76.34" ? { ideVersion: "2.76.34" } : {}
12982
+ ..."2.76.36" ? { ideVersion: "2.76.36" } : {}
12265
12983
  };
12266
12984
  try {
12267
12985
  const res = await _postJson(`${API_BASE2}/api/plugin/heartbeat`, body, this.pollSecretHeader());
@@ -26033,6 +26751,18 @@ var ONE_M_CREDITS_MESSAGE = "\u{1F504} **Reconnect your Claude subscription to c
26033
26751
  var TURN_FAILURE_MESSAGE = "\u26A0\uFE0F **The agent hit an error and couldn\u2019t finish this turn.** Please send your message again.";
26034
26752
  var IDLE_TURN_FAILURE_MESSAGE = "\u26A0\uFE0F **The agent stopped responding, so this turn was ended.** Your message wasn\u2019t answered \u2014 send it again.";
26035
26753
  var IDLE_TURN_RE = /ACP prompt idle/;
26754
+ var AGENT_FREE_LIMIT_RE = /Free usage limit reached/i;
26755
+ function looksLikeAgentFreeLimit(text) {
26756
+ return AGENT_FREE_LIMIT_RE.test(text);
26757
+ }
26758
+ var AGENT_FREE_LIMIT_MESSAGE = [
26759
+ "\u26A0\uFE0F **OpenCode\u2019s free models have hit their usage limit, so this message wasn\u2019t answered.**",
26760
+ "",
26761
+ "Messages will keep failing until the limit resets. To keep going now:",
26762
+ "\u2022 add your own provider key for opencode in **Profile \u203A Agents** (OpenRouter, Anthropic, OpenAI, \u2026),",
26763
+ "\u2022 switch this session to another agent, or",
26764
+ "\u2022 subscribe to OpenCode Go: [opencode.ai/go](https://opencode.ai/go)."
26765
+ ].join("\n");
26036
26766
  function emptyReplyMessage(agent) {
26037
26767
  const label = isKnownAgentId(agent) ? AGENT_REGISTRY[agent].displayName : agent;
26038
26768
  const binary = isKnownAgentId(agent) ? AGENT_REGISTRY[agent].binaryName : agent;
@@ -26096,6 +26826,9 @@ function failureBubble(opts) {
26096
26826
  return houseAgentLimitMessage(`${opts.detail}
26097
26827
  ${opts.recentStderr}`);
26098
26828
  }
26829
+ if (looksLikeAgentFreeLimit(opts.detail) || looksLikeAgentFreeLimit(opts.recentStderr)) {
26830
+ return AGENT_FREE_LIMIT_MESSAGE;
26831
+ }
26099
26832
  if (looksLikeByoProviderBilling(opts.detail, env) || looksLikeByoProviderBilling(opts.recentStderr, env)) {
26100
26833
  return byoProviderBillingMessage(byoProviderName({ env, agent: opts.agent }));
26101
26834
  }
@@ -26438,470 +27171,22 @@ async function startInspectorProxy(opts) {
26438
27171
 
26439
27172
  // src/services/preview/inspector-client.ts
26440
27173
  var INSPECTOR_CHANNEL = "codeam-inspector";
27174
+ var INSPECTOR_MAX_MARKS = 4;
27175
+ var INSPECTOR_ACCENT = "#a855f7";
26441
27176
  function inspectorClientSource(opts) {
26442
- const origins = JSON.stringify(opts.allowedOrigins);
26443
- const channel = JSON.stringify(INSPECTOR_CHANNEL);
27177
+ const script = pointPickerScript({
27178
+ channel: INSPECTOR_CHANNEL,
27179
+ channelField: "source",
27180
+ allowedParents: opts.allowedOrigins,
27181
+ mode: "multi",
27182
+ maxMarks: INSPECTOR_MAX_MARKS,
27183
+ accent: INSPECTOR_ACCENT,
27184
+ agentHighlights: true,
27185
+ reactSource: true,
27186
+ globalName: "__codeamInspector"
27187
+ });
26444
27188
  return `<script id="codeam-inspector" data-codeam="inspector">
26445
- (function () {
26446
- 'use strict';
26447
- try {
26448
- var CHANNEL = ${channel};
26449
- var ALLOWED = ${origins};
26450
-
26451
- var enabled = false;
26452
- var driver = null; // el origen que nos encendio
26453
- var overlay = null;
26454
- var label = null;
26455
- var current = null;
26456
-
26457
- function allowed(origin) {
26458
- return ALLOWED.indexOf(origin) !== -1;
26459
- }
26460
-
26461
- // \xBFEstamos dentro del WebView de la app movil?
26462
- //
26463
- // \u26A0\uFE0F En un WebView el documento es de NIVEL SUPERIOR, asi que
26464
- // \`window.parent === window\` y la via del iframe no existe: el script
26465
- // hablaba solo, y en movil el inspector no devolvia nada. React Native
26466
- // expone su propio canal, que es el unico que sale de ahi.
26467
- function rnBridge() {
26468
- return typeof window.ReactNativeWebView !== 'undefined' &&
26469
- window.ReactNativeWebView &&
26470
- typeof window.ReactNativeWebView.postMessage === 'function'
26471
- ? window.ReactNativeWebView
26472
- : null;
26473
- }
26474
-
26475
- function send(type, payload) {
26476
- if (!driver) return;
26477
- var msg = { source: CHANNEL, type: type };
26478
- for (var k in payload) if (Object.prototype.hasOwnProperty.call(payload, k)) msg[k] = payload[k];
26479
-
26480
- var rn = rnBridge();
26481
- if (rn) {
26482
- // El canal de RN transporta CADENAS, no objetos: pasarle el objeto
26483
- // llega al otro lado como "[object Object]".
26484
- rn.postMessage(JSON.stringify(msg));
26485
- return;
26486
- }
26487
- if (window.parent === window) return;
26488
- window.parent.postMessage(msg, driver);
26489
- }
26490
-
26491
- // El resaltado. Un div fijo por encima de todo que NUNCA captura el
26492
- // puntero: si lo capturara, el elemento de debajo dejaria de recibir el
26493
- // mousemove y el resaltado se quedaria pegado al primero que tocara.
26494
- function ensureOverlay() {
26495
- if (overlay) return;
26496
- overlay = document.createElement('div');
26497
- overlay.setAttribute('data-codeam', 'inspector-overlay');
26498
- overlay.style.cssText = [
26499
- 'position:fixed', 'pointer-events:none', 'z-index:2147483647',
26500
- 'border:1px solid #a855f7', 'background:rgba(168,85,247,0.14)',
26501
- 'border-radius:2px', 'transition:none', 'display:none'
26502
- ].join(';');
26503
- label = document.createElement('div');
26504
- label.setAttribute('data-codeam', 'inspector-label');
26505
- label.style.cssText = [
26506
- 'position:fixed', 'pointer-events:none', 'z-index:2147483647',
26507
- 'background:#a855f7', 'color:#000', 'font:600 11px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace',
26508
- 'padding:2px 6px', 'border-radius:3px', 'white-space:nowrap', 'display:none'
26509
- ].join(';');
26510
- document.body.appendChild(overlay);
26511
- document.body.appendChild(label);
26512
- }
26513
-
26514
- function removeOverlay() {
26515
- if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay);
26516
- if (label && label.parentNode) label.parentNode.removeChild(label);
26517
- overlay = null;
26518
- label = null;
26519
- }
26520
-
26521
- // Fichero y linea del componente de React, cuando el dev server los
26522
- // expone. Es lo que hace que el agente reciba
26523
- // "components/whatsapp-button.tsx:42" en vez de "x:0.62 y:0.81".
26524
- // Solo existe en builds de desarrollo; en produccion devuelve null y se
26525
- // cae al selector CSS.
26526
- function reactSource(el) {
26527
- try {
26528
- for (var key in el) {
26529
- if (key.indexOf('__reactFiber$') !== 0) continue;
26530
- var fiber = el[key];
26531
- while (fiber) {
26532
- var src = fiber._debugSource;
26533
- if (src && src.fileName) {
26534
- return { file: String(src.fileName), line: Number(src.lineNumber) || null };
26535
- }
26536
- fiber = fiber.return;
26537
- }
26538
- }
26539
- } catch (e) { /* nunca romper la pagina por esto */ }
26540
- return null;
26541
- }
26542
-
26543
- // Un selector corto y legible, el respaldo cuando no hay React.
26544
- function selectorFor(el) {
26545
- if (el.id) return '#' + el.id;
26546
- var name = el.tagName ? el.tagName.toLowerCase() : 'node';
26547
- var cls = (el.className && typeof el.className === 'string')
26548
- ? el.className.trim().split(/\\s+/).slice(0, 3).join('.')
26549
- : '';
26550
- return cls ? name + '.' + cls : name;
26551
- }
26552
-
26553
- function describe(el) {
26554
- var r = el.getBoundingClientRect();
26555
- var text = (el.textContent || '').replace(/\\s+/g, ' ').trim();
26556
- return {
26557
- tag: el.tagName ? el.tagName.toLowerCase() : 'node',
26558
- id: el.id || null,
26559
- classes: (el.className && typeof el.className === 'string') ? el.className.trim() : '',
26560
- selector: selectorFor(el),
26561
- // Un recorte, no el contenido: describir no es extraer.
26562
- text: text.length > 120 ? text.slice(0, 120) + '\u2026' : text,
26563
- rect: { x: r.left, y: r.top, w: r.width, h: r.height },
26564
- viewport: { w: window.innerWidth, h: window.innerHeight },
26565
- source: reactSource(el)
26566
- };
26567
- }
26568
-
26569
- function paint(el) {
26570
- ensureOverlay();
26571
- var r = el.getBoundingClientRect();
26572
- overlay.style.display = 'block';
26573
- overlay.style.left = r.left + 'px';
26574
- overlay.style.top = r.top + 'px';
26575
- overlay.style.width = r.width + 'px';
26576
- overlay.style.height = r.height + 'px';
26577
- label.style.display = 'block';
26578
- label.textContent = selectorFor(el) + ' ' + Math.round(r.width) + '\xD7' + Math.round(r.height);
26579
- // Encima si cabe; si no, dentro. Una etiqueta cortada por el borde
26580
- // superior no se lee.
26581
- var above = r.top >= 20;
26582
- label.style.left = r.left + 'px';
26583
- label.style.top = (above ? r.top - 20 : r.top + 2) + 'px';
26584
- }
26585
-
26586
- function onMove(e) {
26587
- if (!enabled) return;
26588
- var el = e.target;
26589
- if (!el || el === current || !el.getBoundingClientRect) return;
26590
- if (el.getAttribute && el.getAttribute('data-codeam')) return;
26591
- current = el;
26592
- paint(el);
26593
- send('hover', { element: describe(el) });
26594
- }
26595
-
26596
- // Captura, y se traga el evento: mientras el modo esta activo un clic
26597
- // SELECCIONA, no navega. Sin esto, pulsar un enlace se llevaria el
26598
- // preview a otra pagina en mitad del gesto.
26599
- /**
26600
- * Las marcas FIJADAS, dibujadas DENTRO de la pagina.
26601
- *
26602
- * \u26A0\uFE0F Antes las pintaba el dashboard, en una capa sobre el iframe. Como esa
26603
- * capa vive fuera del documento, sus coordenadas son del VIEWPORT: al
26604
- * hacer scroll dentro del preview las marcas se quedaban clavadas en la
26605
- * pantalla mientras el elemento se iba (reportado el 2026-08-29).
26606
- *
26607
- * Aqui se anclan al DOCUMENTO (position:absolute mas el scroll sumado), asi
26608
- * que acompa\xF1an al contenido igual que cualquier otro nodo de la pagina \u2014 y
26609
- * ademas se recolocan si el layout cambia, que una capa externa no puede
26610
- * saber.
26611
- */
26612
- var picked = [];
26613
- var seq = 0;
26614
- var marksHost = null;
26615
-
26616
- function ensureMarksHost() {
26617
- if (marksHost && marksHost.parentNode) return marksHost;
26618
- marksHost = document.createElement('div');
26619
- marksHost.setAttribute('data-codeam', 'marks');
26620
- marksHost.style.cssText = 'position:absolute;left:0;top:0;width:0;height:0;z-index:2147483646';
26621
- document.body.appendChild(marksHost);
26622
- return marksHost;
26623
- }
26624
-
26625
- function redrawMarks() {
26626
- if (picked.length === 0) {
26627
- if (marksHost && marksHost.parentNode) marksHost.parentNode.removeChild(marksHost);
26628
- marksHost = null;
26629
- return;
26630
- }
26631
- var host = ensureMarksHost();
26632
- host.innerHTML = '';
26633
- for (var i = 0; i < picked.length; i++) {
26634
- var r = picked[i].el.getBoundingClientRect();
26635
- // Coordenadas de DOCUMENTO: es lo que hace que la marca viaje con el
26636
- // contenido en vez de quedarse pegada a la pantalla.
26637
- var top = r.top + (window.scrollY || window.pageYOffset || 0);
26638
- var left = r.left + (window.scrollX || window.pageXOffset || 0);
26639
-
26640
- var box = document.createElement('div');
26641
- box.setAttribute('data-codeam', 'mark');
26642
- box.style.cssText = [
26643
- 'position:absolute', 'pointer-events:none', 'box-sizing:border-box',
26644
- 'border:1.5px solid #a855f7', 'background:rgba(168,85,247,0.12)',
26645
- 'border-radius:3px',
26646
- 'left:' + left + 'px', 'top:' + top + 'px',
26647
- 'width:' + r.width + 'px', 'height:' + r.height + 'px'
26648
- ].join(';');
26649
-
26650
- var badge = document.createElement('div');
26651
- badge.setAttribute('data-codeam', 'mark-badge');
26652
- badge.style.cssText = [
26653
- 'position:absolute', 'left:-10px', 'top:-10px', 'width:20px', 'height:20px',
26654
- 'border-radius:10px', 'background:#a855f7', 'color:#000',
26655
- 'font:700 11px/20px ui-monospace,SFMono-Regular,Menlo,monospace',
26656
- 'text-align:center', 'pointer-events:none'
26657
- ].join(';');
26658
- badge.textContent = String(i + 1);
26659
-
26660
- box.appendChild(badge);
26661
- host.appendChild(box);
26662
- }
26663
- }
26664
-
26665
- function reposition() { if (picked.length) redrawMarks(); }
26666
-
26667
- // Captura, y se traga el evento: mientras el modo esta activo un clic
26668
- // SELECCIONA, no navega. Sin esto, pulsar un enlace se llevaria el
26669
- // preview a otra pagina en mitad del gesto.
26670
- function onClick(e) {
26671
- if (!enabled) return;
26672
- var el = e.target;
26673
- if (el && el.getAttribute && el.getAttribute('data-codeam')) return;
26674
- e.preventDefault();
26675
- e.stopPropagation();
26676
- if (!el) return;
26677
- var id = 'p' + (++seq);
26678
- picked.push({ id: id, el: el });
26679
- redrawMarks();
26680
- var described = describe(el);
26681
- described.markId = id;
26682
- send('pick', { element: described });
26683
- }
26684
-
26685
- /**
26686
- * El dashboard manda la lista de ids que SIGUEN vivos, en su orden.
26687
- *
26688
- * Es la otra mitad de \xABla marca es un objeto\xBB: si el usuario borra un chip
26689
- * en el composer, la marca tiene que desaparecer de la pagina \u2014 y las que
26690
- * quedan renumerarse, porque lo que el usuario escribe (\xAB\u2460 se sale\xBB) mira
26691
- * a estos numeros.
26692
- */
26693
- function syncMarks(ids) {
26694
- var byId = {};
26695
- for (var i = 0; i < picked.length; i++) byId[picked[i].id] = picked[i];
26696
- var next = [];
26697
- for (var j = 0; j < ids.length; j++) if (byId[ids[j]]) next.push(byId[ids[j]]);
26698
- picked = next;
26699
- redrawMarks();
26700
- }
26701
-
26702
- /**
26703
- * Las marcas del AGENTE \u2014 la herramienta highlight_element.
26704
- *
26705
- * Van aparte de las del usuario a proposito: no se numeran, no se pueden
26706
- * borrar desde el composer y no dependen de que el modo inspector este
26707
- * encendido. Son el agente SE\xD1ALANDO algo, y el usuario las ve aunque no
26708
- * haya tocado nada: un cursor que viaja hasta el elemento, lo centra en
26709
- * pantalla y deja una caja con su etiqueta.
26710
- *
26711
- * Mismo anclaje que las marcas fijadas (coordenadas de documento), asi que
26712
- * acompa\xF1an al scroll. Un selector que no encuentra nada no pinta nada: el
26713
- * agente ya sabe por la herramienta que puede fallar.
26714
- */
26715
- var AGENT = '#22d3ee';
26716
- var agentMarks = [];
26717
- var agentHost = null;
26718
- var agentCursor = null;
26719
- var agentStyle = null;
26720
- var agentListening = false;
26721
-
26722
- function ensureAgentStyle() {
26723
- if (agentStyle && agentStyle.parentNode) return;
26724
- agentStyle = document.createElement('style');
26725
- agentStyle.setAttribute('data-codeam', 'agent-style');
26726
- agentStyle.textContent =
26727
- '@keyframes codeam-agent-pulse{0%{box-shadow:0 0 0 0 rgba(34,211,238,.55)}' +
26728
- '70%{box-shadow:0 0 0 10px rgba(34,211,238,0)}100%{box-shadow:0 0 0 0 rgba(34,211,238,0)}}' +
26729
- '@keyframes codeam-agent-in{from{opacity:0;transform:scale(1.06)}to{opacity:1;transform:scale(1)}}';
26730
- (document.head || document.documentElement).appendChild(agentStyle);
26731
- }
26732
-
26733
- function ensureAgentCursor() {
26734
- if (agentCursor && agentCursor.parentNode) return agentCursor;
26735
- agentCursor = document.createElement('div');
26736
- agentCursor.setAttribute('data-codeam', 'agent-cursor');
26737
- agentCursor.style.cssText = [
26738
- 'position:fixed', 'pointer-events:none', 'z-index:2147483647',
26739
- 'left:' + Math.round(window.innerWidth / 2) + 'px', 'top:' + Math.round(window.innerHeight - 40) + 'px',
26740
- 'transition:left .6s cubic-bezier(.22,1,.36,1),top .6s cubic-bezier(.22,1,.36,1),opacity .3s',
26741
- 'opacity:0'
26742
- ].join(';');
26743
- agentCursor.innerHTML =
26744
- '<svg width="22" height="22" viewBox="0 0 24 24" style="display:block;filter:drop-shadow(0 1px 2px rgba(0,0,0,.5))">' +
26745
- '<path d="M4 2l15 8.5-6.6 1.6L9 18.5z" fill="' + AGENT + '" stroke="#000" stroke-width="1.2"/></svg>' +
26746
- '<div style="margin:2px 0 0 14px;background:' + AGENT + ';color:#000;font:700 10px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;' +
26747
- 'padding:1px 6px;border-radius:8px;white-space:nowrap">Agent</div>';
26748
- document.body.appendChild(agentCursor);
26749
- return agentCursor;
26750
- }
26751
-
26752
- function redrawAgentMarks() {
26753
- if (agentMarks.length === 0) {
26754
- if (agentHost && agentHost.parentNode) agentHost.parentNode.removeChild(agentHost);
26755
- agentHost = null;
26756
- return;
26757
- }
26758
- if (!agentHost || !agentHost.parentNode) {
26759
- agentHost = document.createElement('div');
26760
- agentHost.setAttribute('data-codeam', 'agent-marks');
26761
- agentHost.style.cssText = 'position:absolute;left:0;top:0;width:0;height:0;z-index:2147483646';
26762
- document.body.appendChild(agentHost);
26763
- }
26764
- agentHost.innerHTML = '';
26765
- var sx = window.scrollX || window.pageXOffset || 0;
26766
- var sy = window.scrollY || window.pageYOffset || 0;
26767
- for (var i = 0; i < agentMarks.length; i++) {
26768
- var m = agentMarks[i];
26769
- if (!m.el.isConnected) continue;
26770
- var r = m.el.getBoundingClientRect();
26771
- var box = document.createElement('div');
26772
- box.setAttribute('data-codeam', 'agent-mark');
26773
- box.style.cssText = [
26774
- 'position:absolute', 'pointer-events:none', 'box-sizing:border-box',
26775
- 'border:2px solid ' + AGENT, 'background:rgba(34,211,238,0.10)', 'border-radius:4px',
26776
- 'left:' + (r.left + sx - 3) + 'px', 'top:' + (r.top + sy - 3) + 'px',
26777
- 'width:' + (r.width + 6) + 'px', 'height:' + (r.height + 6) + 'px',
26778
- 'animation:codeam-agent-in .25s ease-out,codeam-agent-pulse 1.6s ease-out 3'
26779
- ].join(';');
26780
- if (m.label) {
26781
- var tag = document.createElement('div');
26782
- tag.setAttribute('data-codeam', 'agent-mark-label');
26783
- tag.style.cssText = [
26784
- 'position:absolute', 'left:-2px', (r.top >= 22 ? 'top:-22px' : 'bottom:-22px'),
26785
- 'background:' + AGENT, 'color:#000', 'font:700 11px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace',
26786
- 'padding:0 6px', 'border-radius:3px', 'white-space:nowrap', 'max-width:260px',
26787
- 'overflow:hidden', 'text-overflow:ellipsis'
26788
- ].join(';');
26789
- tag.textContent = m.label;
26790
- box.appendChild(tag);
26791
- }
26792
- agentHost.appendChild(box);
26793
- }
26794
- }
26795
-
26796
- function listenAgentLayout() {
26797
- if (agentListening) return;
26798
- agentListening = true;
26799
- window.addEventListener('scroll', redrawAgentMarks, true);
26800
- window.addEventListener('resize', redrawAgentMarks);
26801
- }
26802
-
26803
- function agentHighlight(selector, labelText) {
26804
- var el = null;
26805
- try { el = document.querySelector(String(selector)); } catch (e) { el = null; }
26806
- if (!el || !el.getBoundingClientRect) return false;
26807
- ensureAgentStyle();
26808
- listenAgentLayout();
26809
- try { el.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' }); } catch (e) { /* viejo */ }
26810
- var cursor = ensureAgentCursor();
26811
- // Tras el scroll suave: el cursor viaja al centro del elemento y, al
26812
- // llegar, aparece la caja. El orden es lo que se lee como \xABel agente
26813
- // lo esta se\xF1alando\xBB y no como un recuadro que salta.
26814
- setTimeout(function () {
26815
- var r = el.getBoundingClientRect();
26816
- cursor.style.opacity = '1';
26817
- cursor.style.left = Math.round(r.left + Math.min(r.width / 2, 40)) + 'px';
26818
- cursor.style.top = Math.round(r.top + Math.min(r.height / 2, 24)) + 'px';
26819
- setTimeout(function () {
26820
- agentMarks.push({ el: el, label: labelText ? String(labelText).slice(0, 80) : '' });
26821
- redrawAgentMarks();
26822
- }, 620);
26823
- }, 380);
26824
- return true;
26825
- }
26826
-
26827
- function agentClear() {
26828
- agentMarks = [];
26829
- redrawAgentMarks();
26830
- if (agentCursor && agentCursor.parentNode) agentCursor.parentNode.removeChild(agentCursor);
26831
- agentCursor = null;
26832
- if (agentStyle && agentStyle.parentNode) agentStyle.parentNode.removeChild(agentStyle);
26833
- agentStyle = null;
26834
- if (agentListening) {
26835
- window.removeEventListener('scroll', redrawAgentMarks, true);
26836
- window.removeEventListener('resize', redrawAgentMarks);
26837
- agentListening = false;
26838
- }
26839
- }
26840
-
26841
- function enable(origin) {
26842
- if (enabled) return;
26843
- enabled = true;
26844
- driver = origin;
26845
- document.addEventListener('mousemove', onMove, true);
26846
- document.addEventListener('click', onClick, true);
26847
- // El scroll y el resize mueven los elementos; las marcas los siguen.
26848
- window.addEventListener('scroll', reposition, true);
26849
- window.addEventListener('resize', reposition);
26850
- send('ready', {});
26851
- }
26852
-
26853
- function disable() {
26854
- if (!enabled) return;
26855
- enabled = false;
26856
- current = null;
26857
- document.removeEventListener('mousemove', onMove, true);
26858
- document.removeEventListener('click', onClick, true);
26859
- window.removeEventListener('scroll', reposition, true);
26860
- window.removeEventListener('resize', reposition);
26861
- removeOverlay();
26862
- // Las marcas fijadas SI sobreviven a salir del modo: siguen siendo el
26863
- // contexto del mensaje que el usuario esta escribiendo.
26864
- driver = null;
26865
- }
26866
-
26867
- window.addEventListener('message', function (e) {
26868
- if (!allowed(e.origin)) return;
26869
- var data = e.data;
26870
- if (!data || data.source !== CHANNEL) return;
26871
- if (data.type === 'enable') enable(e.origin);
26872
- else if (data.type === 'disable') disable();
26873
- else if (data.type === 'marks' && data.ids) syncMarks(data.ids);
26874
- else if (data.type === 'agent-highlight' && data.selector) agentHighlight(data.selector, data.label);
26875
- else if (data.type === 'agent-clear') agentClear();
26876
- });
26877
-
26878
- /**
26879
- * La puerta para React Native, que no puede usar \`postMessage\`.
26880
- *
26881
- * \u26A0\uFE0F Aqui NO hay lista blanca de origenes, y no es un descuido: quien
26882
- * llama esto es la app anfitriona sobre SU PROPIO WebView, via
26883
- * \`injectJavaScript\` \u2014 no hay un tercero al que filtrar. La lista blanca
26884
- * protege el caso del iframe, donde cualquier pagina puede embeber el
26885
- * preview y mandar mensajes; ese caso sigue exactamente igual de cerrado.
26886
- *
26887
- * Solo se expone donde ese puente existe, para no dejar en una pagina
26888
- * normal una funcion global que encienda el inspector sin permiso.
26889
- */
26890
- if (rnBridge()) {
26891
- window.__codeamInspector = {
26892
- enable: function () { enable('react-native'); },
26893
- disable: disable,
26894
- // La app movil ya llamaba \`marks\` al borrar un chip y no existia:
26895
- // la marca se quedaba pintada en la pagina.
26896
- marks: function (ids) { if (ids && ids.length !== undefined) syncMarks(ids); },
26897
- highlight: function (selector, text) { return agentHighlight(selector, text); },
26898
- clear: agentClear,
26899
- };
26900
- }
26901
- } catch (e) {
26902
- // Pase lo que pase, la app del usuario sigue funcionando.
26903
- }
26904
- })();
27189
+ ${script}
26905
27190
  </script>`;
26906
27191
  }
26907
27192
 
@@ -31845,6 +32130,7 @@ function isLocalSession(env = process.env) {
31845
32130
 
31846
32131
  // src/agents/acp/adapters.ts
31847
32132
  var require_ = require;
32133
+ var OPENCODE_FATAL_TURN_STDERR_RE = /message="stream error".*error\.error="[^"]*(Free usage limit reached|insufficient[_ ]quota|invalid[_ ]api[_ ]key|incorrect api key|unauthorized)/i;
31848
32134
  function claudeBinaryWaiter(opts = {}) {
31849
32135
  return waitForClaudeNativeBinary(opts).then((p2) => p2 !== null);
31850
32136
  }
@@ -32004,8 +32290,12 @@ var REGISTRY = {
32004
32290
  // the ACP client spawns the adapter with `env: { ...process.env, ...extraEnv }`.
32005
32291
  opencode: () => ({
32006
32292
  command: "opencode",
32007
- args: ["acp"],
32293
+ // `--print-logs --log-level ERROR`: opencode writes its provider errors only
32294
+ // to its own log file, so without these flags they never reach the adapter
32295
+ // stderr we read (see OPENCODE_FATAL_TURN_STDERR_RE).
32296
+ args: ["acp", "--print-logs", "--log-level", "ERROR"],
32008
32297
  requiresAgentBinary: "opencode",
32298
+ fatalTurnStderr: OPENCODE_FATAL_TURN_STDERR_RE,
32009
32299
  waitForBinary: async (o) => {
32010
32300
  if (isLocalSession()) await ensureOpencodeInstalled();
32011
32301
  augmentOpencodePath();
@@ -37132,6 +37422,9 @@ var _acpStartSeam = {
37132
37422
  var PROTOCOL_VERSION3 = 1;
37133
37423
  var FATAL_STARTUP_RE = /IneligibleTierError|UNSUPPORTED_CLIENT|no longer supported for Gemini Code Assist|not eligible for Gemini Code Assist|Error authenticating|ProjectIdRequiredError/i;
37134
37424
  var NEWSESSION_TIMEOUT_MS = 12e4;
37425
+ function agentErrorOf(line) {
37426
+ return /error\.error="([^"]+)"/.exec(line)?.[1] ?? line;
37427
+ }
37135
37428
  var MCP_STARTUP_TIMEOUT_MS = NEWSESSION_TIMEOUT_MS - 3e4;
37136
37429
  var AGENT_ENV_WITHHELD = ["PREVIEW_TUNNEL_TOKEN", "PREVIEW_TUNNEL_HOSTNAME"];
37137
37430
  var LOADSESSION_TIMEOUT_MS = 6e4;
@@ -37237,6 +37530,12 @@ var AcpClient = class {
37237
37530
  * engages; with it, the crash rejects immediately → retry respawns once the
37238
37531
  * install settles. Broader than {@link startupFailureReject} (newSession-only). */
37239
37532
  startAbortReject = null;
37533
+ /** The adapter's {@link AdapterSpec.fatalTurnStderr}, set at spawn. */
37534
+ fatalTurnStderr = null;
37535
+ /** Set while a `session/prompt` is in flight; the stderr watcher calls it on
37536
+ * a {@link fatalTurnStderr} line to end the turn now (opencode keeps a turn
37537
+ * open forever after a terminal provider error — see the adapter spec). */
37538
+ turnFailureReject = null;
37240
37539
  /**
37241
37540
  * Spawn the adapter + handshake, retrying a TRANSIENT in-adapter spawn race.
37242
37541
  *
@@ -37325,6 +37624,7 @@ var AcpClient = class {
37325
37624
  "acpClient",
37326
37625
  `provider routing ${formatProviderRouting(describeProviderRouting(childEnv, { cwd }), adapter.command)}`
37327
37626
  );
37627
+ this.fatalTurnStderr = adapter.fatalTurnStderr ?? null;
37328
37628
  const child = (0, import_node_child_process28.spawn)(adapter.command, adapter.args, {
37329
37629
  cwd,
37330
37630
  env: childEnv,
@@ -37350,16 +37650,7 @@ var AcpClient = class {
37350
37650
  child.stderr?.on("data", (chunk) => {
37351
37651
  for (const line of chunk.split(/\r?\n/)) {
37352
37652
  const trimmed = line.trim();
37353
- if (trimmed) {
37354
- log.info("acpAdapter", trimmed);
37355
- this.opts.onStderr?.(trimmed);
37356
- this.recentStderr.push(trimmed);
37357
- if (this.recentStderr.length > 12) this.recentStderr.shift();
37358
- if (this.startupFailureReject && FATAL_STARTUP_RE.test(trimmed)) {
37359
- this.startupFailureReject(new Error(`AGENT_STARTUP_FAILED: ${trimmed}`));
37360
- this.startupFailureReject = null;
37361
- }
37362
- }
37653
+ if (trimmed) this.onAdapterStderrLine(trimmed);
37363
37654
  }
37364
37655
  });
37365
37656
  child.on("exit", (code, signal) => {
@@ -37559,9 +37850,16 @@ var AcpClient = class {
37559
37850
  PROMPT_ACTIVE_IDLE_TIMEOUT_MS
37560
37851
  );
37561
37852
  this.promptIdle = idle;
37853
+ let fatalTripped = false;
37854
+ const fatal = new Promise((_2, reject) => {
37855
+ this.turnFailureReject = (err) => {
37856
+ fatalTripped = true;
37857
+ reject(err);
37858
+ };
37859
+ });
37562
37860
  this.pendingToolCalls.clear();
37563
37861
  try {
37564
- const result = await Promise.race([send, idle.promise]);
37862
+ const result = await Promise.race([send, idle.promise, fatal]);
37565
37863
  await new Promise((resolve13) => setImmediate(resolve13));
37566
37864
  log.info(
37567
37865
  "acpClient",
@@ -37575,15 +37873,32 @@ var AcpClient = class {
37575
37873
  "acpClient",
37576
37874
  `prompt \u2190 failed elapsedMs=${Date.now() - t0} err=${err instanceof Error ? err.message : String(err)}`
37577
37875
  );
37578
- if (idleTripped) await this.cancelAbandonedTurn(sessionId, send);
37876
+ if (idleTripped || fatalTripped) await this.cancelAbandonedTurn(sessionId, send);
37579
37877
  throw err;
37580
37878
  } finally {
37581
37879
  idle.clear();
37582
37880
  this.promptIdle = null;
37881
+ this.turnFailureReject = null;
37882
+ }
37883
+ }
37884
+ /** One non-empty adapter stderr line: logged, kept in the ring, and checked
37885
+ * for the fatal startup / fatal turn errors that end a stuck RPC early. */
37886
+ onAdapterStderrLine(trimmed) {
37887
+ log.info("acpAdapter", trimmed);
37888
+ this.opts.onStderr?.(trimmed);
37889
+ this.recentStderr.push(trimmed);
37890
+ if (this.recentStderr.length > 12) this.recentStderr.shift();
37891
+ if (this.startupFailureReject && FATAL_STARTUP_RE.test(trimmed)) {
37892
+ this.startupFailureReject(new Error(`AGENT_STARTUP_FAILED: ${trimmed}`));
37893
+ this.startupFailureReject = null;
37894
+ }
37895
+ if (this.turnFailureReject && this.fatalTurnStderr?.test(trimmed)) {
37896
+ this.turnFailureReject(new Error(`AGENT_TURN_FAILED: ${agentErrorOf(trimmed)}`));
37897
+ this.turnFailureReject = null;
37583
37898
  }
37584
37899
  }
37585
37900
  /**
37586
- * Cancel a turn the idle watchdog abandoned and wait, bounded, for the
37901
+ * Cancel a turn the idle watchdog (or a fatal stderr error) abandoned and wait, bounded, for the
37587
37902
  * adapter to answer its `session/prompt` — the proof the turn is over before
37588
37903
  * the next prompt is sent. Best-effort: a dead adapter can't hold the queue
37589
37904
  * longer than {@link ABANDONED_TURN_SETTLE_MS}.
@@ -43678,7 +43993,7 @@ async function runStartTaskTurn(ctx, payload, blocks, promptText) {
43678
43993
  reportTurn2(
43679
43994
  ctx,
43680
43995
  "failed",
43681
- bubble === AUTH_FAILURE_MESSAGE ? "AUTH_FAILED" : bubble === ONE_M_CREDITS_MESSAGE ? "ONE_M_CREDITS" : failed.partialReplyKept ? "TURN_ERROR_PARTIAL" : "TURN_ERROR"
43996
+ bubble === AUTH_FAILURE_MESSAGE ? "AUTH_FAILED" : bubble === ONE_M_CREDITS_MESSAGE ? "ONE_M_CREDITS" : bubble === AGENT_FREE_LIMIT_MESSAGE ? "AGENT_FREE_LIMIT" : failed.partialReplyKept ? "TURN_ERROR_PARTIAL" : "TURN_ERROR"
43682
43997
  );
43683
43998
  if (bubble === AUTH_FAILURE_MESSAGE || bubble === ONE_M_CREDITS_MESSAGE) {
43684
43999
  void reportCredentialInvalid(opts);
@@ -51622,7 +51937,7 @@ async function autoUpgradeBeforeCriticalCommand() {
51622
51937
  if (process.env.NODE_ENV === "test") return;
51623
51938
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
51624
51939
  if (process.env.CI) return;
51625
- const current2 = true ? "2.76.34" : null;
51940
+ const current2 = true ? "2.76.36" : null;
51626
51941
  if (!current2) return;
51627
51942
  const cache = readCache();
51628
51943
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -51639,7 +51954,7 @@ function checkForUpdates() {
51639
51954
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
51640
51955
  if (process.env.CI) return;
51641
51956
  if (!process.stdout.isTTY) return;
51642
- const current2 = true ? "2.76.34" : null;
51957
+ const current2 = true ? "2.76.36" : null;
51643
51958
  if (!current2) return;
51644
51959
  const cache = readCache();
51645
51960
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -51945,7 +52260,7 @@ var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
51945
52260
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
51946
52261
  var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
51947
52262
  function currentCliVersion() {
51948
- return true ? "2.76.34" : null;
52263
+ return true ? "2.76.36" : null;
51949
52264
  }
51950
52265
  async function installedVersion(deps) {
51951
52266
  const ls = await deps.run(
@@ -52168,7 +52483,7 @@ function signalHostReady(send = process.send?.bind(process)) {
52168
52483
  }
52169
52484
  function defaultDeps() {
52170
52485
  return {
52171
- ownVersion: true ? "2.76.34" : null,
52486
+ ownVersion: true ? "2.76.36" : null,
52172
52487
  root: cliVersionsRoot(),
52173
52488
  args: process.argv.slice(3),
52174
52489
  execPath: process.execPath,
@@ -54706,7 +55021,7 @@ function checkChokidar() {
54706
55021
  }
54707
55022
  async function doctor(args2 = []) {
54708
55023
  const json = args2.includes("--json");
54709
- const cliVersion = true ? "2.76.34" : "0.0.0-dev";
55024
+ const cliVersion = true ? "2.76.36" : "0.0.0-dev";
54710
55025
  const apiBase2 = resolveApiBaseUrl();
54711
55026
  const diagnosticId = (0, import_node_crypto17.randomUUID)();
54712
55027
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -55242,7 +55557,7 @@ async function mcpWarm(argv = [], opts = {}) {
55242
55557
  // src/commands/version.ts
55243
55558
  var import_picocolors15 = __toESM(require("picocolors"));
55244
55559
  function version2() {
55245
- const v = true ? "2.76.34" : "unknown";
55560
+ const v = true ? "2.76.36" : "unknown";
55246
55561
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
55247
55562
  }
55248
55563