From 4730e60a388de2e7a99c63b8b185728a8503ca1f Mon Sep 17 00:00:00 2001 From: phase3dev <77866949+phase3dev@users.noreply.github.com> Date: Wed, 10 Jun 2026 02:15:38 -1000 Subject: [PATCH 1/7] fix(md-copy): leading semicolon on the injected payload (ASI safety) When appended after a bundle whose final statement is an expression with no trailing semicolon, the IIFE would parse as a call on that expression and throw before the webview app initializes. Prefix the payload with ';' so the patch is safe regardless of the bundle's final token. Regenerated the three embeds; adds a node-exec test on a no-semicolon bundle. Not triggered by the current 2.1.170 bundle (it ends with '}'); this hardens against future builds. Codex PR review, P2. Co-Authored-By: Claude Opus 4.8 (1M context) --- fixes/markdown-copy-export/add-md-copy.py | 2 +- fixes/markdown-copy-export/webview-inject.js | 5 ++++- launcher/claudemax | 5 ++++- launcher/claudemax.win.js | 2 +- tests/test_md_patcher.py | 11 +++++++++++ 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/fixes/markdown-copy-export/add-md-copy.py b/fixes/markdown-copy-export/add-md-copy.py index 2958e3e..619b3fc 100644 --- a/fixes/markdown-copy-export/add-md-copy.py +++ b/fixes/markdown-copy-export/add-md-copy.py @@ -31,7 +31,7 @@ BACKUP_SUFFIX = ".bak-md-copy" # >>>CCWA-MD-COPY-EMBED>>> (generated by tools/gen-embeds; do not edit) -INJECT_JS = base64.b64decode("LyogY2MtbWQtY29weTogcGVyLW1lc3NhZ2UgYW5kIHdob2xlLWNvbnZlcnNhdGlvbiBjb3B5IChtYXJrZG93bi9wbGFpbikgZm9yIHRoZQogKiBDbGF1ZGUgQ29kZSBWUyBDb2RlIHdlYnZpZXcuIFNlbGYtY29udGFpbmVkIElJRkUgYXBwZW5kZWQgdG8gd2Vidmlldy9pbmRleC5qcy4KICogQWRkaXRpdmUgYW5kIHJlYWQtb25seSB3LnIudC4gYXBwIHN0YXRlOyBrZXllZCBvbiBzdGFibGUgQ1NTLW1vZHVsZSBjbGFzcwogKiBwcmVmaXhlcywgc28gaXQgZmFpbHMgc2FmZSAoY29udHJvbHMgc2ltcGx5IGRvIG5vdCBhcHBlYXIpIGlmIGEgcHJlZml4IG1vdmVzLgogKiBFeHBvc2VzIGl0cyBwdXJlIGZ1bmN0aW9ucyBmb3Igbm9kZSB1bml0IHRlc3RzOyBib290KClzIG9ubHkgaW4gYSByZWFsIHdlYnZpZXcuICovCihmdW5jdGlvbiAoKSB7CiAgInVzZSBzdHJpY3QiOwoKICB2YXIgQ09OVFJPTF9QUkVGSVggPSAiY2MtbWQtY29weSI7IC8vIGV2ZXJ5IGluamVjdGVkIG5vZGUncyBjbGFzcyBzdGFydHMgd2l0aCB0aGlzCiAgdmFyIFVTRVJfQlVCQkxFID0gJ1tjbGFzcyo9InVzZXJNZXNzYWdlQ29udGFpbmVyXyJdJzsKICAvLyBBc3Npc3RhbnQgbWVzc2FnZSB3cmFwcGVyLiBWZXJpZmllZCBvbiAyLjEuMTcwOiB0aGUgcmVuZGVyIGVtaXRzIGV4YWN0bHkgb25lCiAgLy8gYGRhdGEtdGVzdGlkPSJhc3Npc3RhbnQtbWVzc2FnZSJgIGRpdiBwZXIgYXNzaXN0YW50IHR1cm4sIHdpdGggdGhlIHJhdGluZwogIC8vIHdpZGdldCBhbmQgY29udGVudCBibG9ja3MgYXMgaXRzIGNoaWxkcmVuLiAoVGhlIGVhcmxpZXIgYFtkYXRhLW1lc3NhZ2UtcmF0aW5nXWAKICAvLyB3YXMgV1JPTkc6IHRoYXQgYXR0cmlidXRlIHNpdHMgb24gdGhlIG5lc3RlZCByYXRpbmcgY29udHJvbCwgd2hpY2ggaXMgYWxzbyBvbmx5CiAgLy8gcmVuZGVyZWQgYmVoaW5kIGFuIGV4cGVyaW1lbnQrYW5hbHl0aWNzIGdhdGUuKSBSZS1waW5uZWQgaW4gVGFzayA2LgogIHZhciBBU1NJU1RBTlRfQlVCQkxFID0gJ1tkYXRhLXRlc3RpZD0iYXNzaXN0YW50LW1lc3NhZ2UiXSc7CiAgdmFyIE1FU1NBR0VTX0NPTlRBSU5FUiA9ICdbY2xhc3MqPSJtZXNzYWdlc0NvbnRhaW5lcl8iXSc7IC8vIGUuZy4gJ1tjbGFzcyo9InRpbWVsaW5lXyJdJzsgIiIgLT4gb2JzZXJ2ZSBkb2N1bWVudC5ib2R5CiAgLy8gT3B0aW9uYWwgbmFycm93aW5nIG9ubHkuIE1VU1QgYmUgYSBzaW5nbGUgd3JhcHBlciBhcm91bmQgQUxMIGNvbnRlbnQgYmxvY2tzLAogIC8vIG5vdCBhIHBlci1ibG9jayBjbGFzcyAoYSB0dXJuIGhhcyBtdWx0aXBsZSBibG9ja3MpLiAiIiAtPiB1c2UgdGhlIGJ1YmJsZSBpdHNlbGYKICAvLyAoYWxyZWFkeSBhZ2dyZWdhdGVzIGFsbCBibG9ja3M7IHNhbml0aXplQ2xvbmUgaXMgdGhlIGNvcnJlY3RuZXNzIGdhdGUpLgogIHZhciBBU1NJU1RBTlRfQ09OVEVOVCA9ICIiOwogIHZhciBGRUVEQkFDS19NUyA9IDE4MDA7CgogIC8vIC0tLS0gSFRNTCAtPiBNYXJrZG93biAoRE9NIHdhbGspIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KICAvLyBVc2VzIG9ubHk6IG5vZGVUeXBlLCB0YWdOYW1lLCBjaGlsZE5vZGVzLCB0ZXh0Q29udGVudCwgZ2V0QXR0cmlidXRlLCBjbGFzc05hbWUuCiAgZnVuY3Rpb24gaHRtbFRvTWFya2Rvd24ocm9vdCkgewogICAgLy8gTG9uZ2VzdCBydW4gb2YgY29uc2VjdXRpdmUgYmFja3RpY2tzIGluIHMsIHNvIGEgY29kZSBkZWxpbWl0ZXIvZmVuY2UgY2FuIGJlCiAgICAvLyBjaG9zZW4gbG9uZ2VyIHRoYW4gYW55dGhpbmcgaW5zaWRlIGl0IChlbHNlIGBgYCBpbiB0aGUgY29udGVudCBjbG9zZXMgZWFybHkpLgogICAgZnVuY3Rpb24gYmFja3RpY2tSdW4ocykgewogICAgICB2YXIgbWF4ID0gMCwgY3VyID0gMDsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgaWYgKHMuY2hhckF0KGkpID09PSAiYCIpIHsgY3VyKys7IGlmIChjdXIgPiBtYXgpIG1heCA9IGN1cjsgfSBlbHNlIGN1ciA9IDA7CiAgICAgIH0KICAgICAgcmV0dXJuIG1heDsKICAgIH0KICAgIGZ1bmN0aW9uIGZlbmNlKHMsIG1pbikgeyB2YXIgbiA9IGJhY2t0aWNrUnVuKHMpICsgMTsgaWYgKG4gPCBtaW4pIG4gPSBtaW47IHJldHVybiBuZXcgQXJyYXkobiArIDEpLmpvaW4oImAiKTsgfQogICAgZnVuY3Rpb24gaW5saW5lKG5vZGUpIHsKICAgICAgdmFyIG91dCA9ICIiOwogICAgICB2YXIga2lkcyA9IG5vZGUuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgdmFyIGMgPSBraWRzW2ldOwogICAgICAgIGlmIChjLm5vZGVUeXBlID09PSAzKSB7IG91dCArPSBjLnRleHRDb250ZW50IHx8ICIiOyBjb250aW51ZTsgfQogICAgICAgIGlmIChjLm5vZGVUeXBlICE9PSAxKSBjb250aW51ZTsKICAgICAgICB2YXIgdGFnID0gKGMudGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKTsKICAgICAgICBpZiAodGFnID09PSAiQlIiKSBvdXQgKz0gIlxuIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJTVFJPTkciIHx8IHRhZyA9PT0gIkIiKSBvdXQgKz0gIioqIiArIGlubGluZShjKSArICIqKiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiRU0iIHx8IHRhZyA9PT0gIkkiKSBvdXQgKz0gIioiICsgaW5saW5lKGMpICsgIioiOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIkRFTCIgfHwgdGFnID09PSAiUyIpIG91dCArPSAifn4iICsgaW5saW5lKGMpICsgIn5+IjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJDT0RFIikgewogICAgICAgICAgdmFyIGN0ID0gYy50ZXh0Q29udGVudCB8fCAiIjsKICAgICAgICAgIHZhciBkID0gZmVuY2UoY3QsIDEpOwogICAgICAgICAgLy8gQ29tbW9uTWFyayBzdHJpcHMgb25lIGxlYWRpbmcrdHJhaWxpbmcgc3BhY2UsIHNvIHBhZCB3aGVuIGFuIGVkZ2UgaXMgYQogICAgICAgICAgLy8gYmFja3RpY2sgdG8ga2VlcCBpdCBmcm9tIG1lcmdpbmcgd2l0aCB0aGUgZGVsaW1pdGVyLgogICAgICAgICAgdmFyIHAgPSAoY3QuY2hhckF0KDApID09PSAiYCIgfHwgY3QuY2hhckF0KGN0Lmxlbmd0aCAtIDEpID09PSAiYCIpID8gIiAiIDogIiI7CiAgICAgICAgICBvdXQgKz0gZCArIHAgKyBjdCArIHAgKyBkOwogICAgICAgIH0KICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJBIikgewogICAgICAgICAgdmFyIGhyZWYgPSBjLmdldEF0dHJpYnV0ZSA/IGMuZ2V0QXR0cmlidXRlKCJocmVmIikgOiBudWxsOwogICAgICAgICAgdmFyIHQgPSBpbmxpbmUoYyk7CiAgICAgICAgICBvdXQgKz0gaHJlZiA/ICJbIiArIHQgKyAiXSgiICsgaHJlZiArICIpIiA6IHQ7CiAgICAgICAgfSBlbHNlIG91dCArPSBpbmxpbmUoYyk7IC8vIHVua25vd24gaW5saW5lIHdyYXBwZXI6IGtlZXAgdGV4dCwgZHJvcCB0YWcKICAgICAgfQogICAgICByZXR1cm4gb3V0OwogICAgfQogICAgZnVuY3Rpb24gbGFuZ09mKGNvZGVFbCkgewogICAgICB2YXIgY2xzID0gIiI7CiAgICAgIGlmIChjb2RlRWwpIGNscyA9IChjb2RlRWwuZ2V0QXR0cmlidXRlICYmIGNvZGVFbC5nZXRBdHRyaWJ1dGUoImNsYXNzIikpIHx8IGNvZGVFbC5jbGFzc05hbWUgfHwgIiI7CiAgICAgIHZhciBtID0gL2xhbmd1YWdlLShbQS1aYS16MC05KyMuXC1dKykvLmV4ZWMoY2xzIHx8ICIiKTsKICAgICAgcmV0dXJuIG0gPyBtWzFdIDogIiI7CiAgICB9CiAgICBmdW5jdGlvbiBmaW5kQ2hpbGRUYWcobm9kZSwgdGFnKSB7CiAgICAgIHZhciBraWRzID0gbm9kZS5jaGlsZE5vZGVzIHx8IFtdOwogICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGtpZHMubGVuZ3RoOyBpKyspIHsKICAgICAgICBpZiAoa2lkc1tpXS5ub2RlVHlwZSA9PT0gMSAmJiAoa2lkc1tpXS50YWdOYW1lIHx8ICIiKS50b1VwcGVyQ2FzZSgpID09PSB0YWcpIHJldHVybiBraWRzW2ldOwogICAgICB9CiAgICAgIHJldHVybiBudWxsOwogICAgfQogICAgZnVuY3Rpb24gbGlzdChub2RlLCBvcmRlcmVkLCBkZXB0aCkgewogICAgICB2YXIgb3V0ID0gIiIsIG4gPSAxOwogICAgICB2YXIga2lkcyA9IG5vZGUuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgdmFyIGxpID0ga2lkc1tpXTsKICAgICAgICBpZiAobGkubm9kZVR5cGUgIT09IDEgfHwgKGxpLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCkgIT09ICJMSSIpIGNvbnRpbnVlOwogICAgICAgIHZhciBtYXJrZXIgPSBvcmRlcmVkID8gbisrICsgIi4gIiA6ICItICI7CiAgICAgICAgdmFyIGluZGVudCA9IG5ldyBBcnJheShkZXB0aCArIDEpLmpvaW4oIiAgIik7CiAgICAgICAgdmFyIGxlYWQgPSAiIiwgbmVzdGVkID0gIiI7CiAgICAgICAgdmFyIGxrID0gbGkuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IGxrLmxlbmd0aDsgaisrKSB7CiAgICAgICAgICB2YXIgY2ggPSBsa1tqXTsKICAgICAgICAgIHZhciBjdCA9IGNoLm5vZGVUeXBlID09PSAxID8gKGNoLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCkgOiAiIjsKICAgICAgICAgIGlmIChjdCA9PT0gIlVMIikgbmVzdGVkICs9IGxpc3QoY2gsIGZhbHNlLCBkZXB0aCArIDEpOwogICAgICAgICAgZWxzZSBpZiAoY3QgPT09ICJPTCIpIG5lc3RlZCArPSBsaXN0KGNoLCB0cnVlLCBkZXB0aCArIDEpOwogICAgICAgICAgZWxzZSBpZiAoY2gubm9kZVR5cGUgPT09IDMpIGxlYWQgKz0gY2gudGV4dENvbnRlbnQgfHwgIiI7CiAgICAgICAgICBlbHNlIGxlYWQgKz0gaW5saW5lKGNoKTsKICAgICAgICB9CiAgICAgICAgb3V0ICs9IGluZGVudCArIG1hcmtlciArIGxlYWQudHJpbSgpICsgIlxuIiArIG5lc3RlZDsKICAgICAgfQogICAgICByZXR1cm4gb3V0OwogICAgfQogICAgZnVuY3Rpb24gdGFibGUobm9kZSkgewogICAgICB2YXIgcm93cyA9IFtdOwogICAgICAoZnVuY3Rpb24gY29sbGVjdChjb250YWluZXIpIHsKICAgICAgICB2YXIga2lkcyA9IGNvbnRhaW5lci5jaGlsZE5vZGVzIHx8IFtdOwogICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwga2lkcy5sZW5ndGg7IGkrKykgewogICAgICAgICAgdmFyIGMgPSBraWRzW2ldOwogICAgICAgICAgaWYgKGMubm9kZVR5cGUgIT09IDEpIGNvbnRpbnVlOwogICAgICAgICAgdmFyIHQgPSAoYy50YWdOYW1lIHx8ICIiKS50b1VwcGVyQ2FzZSgpOwogICAgICAgICAgaWYgKHQgPT09ICJUSEVBRCIgfHwgdCA9PT0gIlRCT0RZIiB8fCB0ID09PSAiVEZPT1QiKSBjb2xsZWN0KGMpOwogICAgICAgICAgZWxzZSBpZiAodCA9PT0gIlRSIikgewogICAgICAgICAgICB2YXIgY2VsbHMgPSBbXSwgY2MgPSBjLmNoaWxkTm9kZXMgfHwgW107CiAgICAgICAgICAgIGZvciAodmFyIGogPSAwOyBqIDwgY2MubGVuZ3RoOyBqKyspIHsKICAgICAgICAgICAgICB2YXIgZCA9IGNjW2pdOwogICAgICAgICAgICAgIGlmIChkLm5vZGVUeXBlICE9PSAxKSBjb250aW51ZTsKICAgICAgICAgICAgICB2YXIgZHQgPSAoZC50YWdOYW1lIHx8ICIiKS50b1VwcGVyQ2FzZSgpOwogICAgICAgICAgICAgIGlmIChkdCA9PT0gIlRIIiB8fCBkdCA9PT0gIlREIikgY2VsbHMucHVzaChpbmxpbmUoZCkudHJpbSgpKTsKICAgICAgICAgICAgfQogICAgICAgICAgICByb3dzLnB1c2goY2VsbHMpOwogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfSkobm9kZSk7CiAgICAgIGlmICghcm93cy5sZW5ndGgpIHJldHVybiAiIjsKICAgICAgdmFyIGhlYWQgPSByb3dzWzBdLCBib2R5ID0gcm93cy5zbGljZSgxKTsKICAgICAgdmFyIHNlcCA9IGhlYWQubWFwKGZ1bmN0aW9uICgpIHsgcmV0dXJuICItLS0iOyB9KTsKICAgICAgdmFyIG91dCA9ICJ8ICIgKyBoZWFkLmpvaW4oIiB8ICIpICsgIiB8XG58ICIgKyBzZXAuam9pbigiIHwgIikgKyAiIHxcbiI7CiAgICAgIGZvciAodmFyIGsgPSAwOyBrIDwgYm9keS5sZW5ndGg7IGsrKykgb3V0ICs9ICJ8ICIgKyBib2R5W2tdLmpvaW4oIiB8ICIpICsgIiB8XG4iOwogICAgICByZXR1cm4gb3V0OwogICAgfQogICAgZnVuY3Rpb24gYmxvY2sobm9kZSkgewogICAgICB2YXIgb3V0ID0gIiI7CiAgICAgIHZhciBraWRzID0gbm9kZS5jaGlsZE5vZGVzIHx8IFtdOwogICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGtpZHMubGVuZ3RoOyBpKyspIHsKICAgICAgICB2YXIgYyA9IGtpZHNbaV07CiAgICAgICAgaWYgKGMubm9kZVR5cGUgPT09IDMpIHsgaWYgKChjLnRleHRDb250ZW50IHx8ICIiKS50cmltKCkpIG91dCArPSBjLnRleHRDb250ZW50OyBjb250aW51ZTsgfQogICAgICAgIGlmIChjLm5vZGVUeXBlICE9PSAxKSBjb250aW51ZTsKICAgICAgICB2YXIgdGFnID0gKGMudGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKTsKICAgICAgICBpZiAoL15IWzEtNl0kLy50ZXN0KHRhZykpIG91dCArPSBuZXcgQXJyYXkoK3RhZ1sxXSArIDEpLmpvaW4oIiMiKSArICIgIiArIGlubGluZShjKS50cmltKCkgKyAiXG5cbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiUCIpIG91dCArPSBpbmxpbmUoYykudHJpbSgpICsgIlxuXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIlVMIikgb3V0ICs9IGxpc3QoYywgZmFsc2UsIDApICsgIlxuIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJPTCIpIG91dCArPSBsaXN0KGMsIHRydWUsIDApICsgIlxuIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJQUkUiKSB7CiAgICAgICAgICB2YXIgY29kZSA9IGZpbmRDaGlsZFRhZyhjLCAiQ09ERSIpOwogICAgICAgICAgdmFyIGxhbmcgPSBsYW5nT2YoY29kZSB8fCBjKTsKICAgICAgICAgIHZhciBib2R5ID0gKGNvZGUgfHwgYykudGV4dENvbnRlbnQgfHwgIiI7CiAgICAgICAgICB2YXIgZiA9IGZlbmNlKGJvZHksIDMpOwogICAgICAgICAgb3V0ICs9IGYgKyBsYW5nICsgIlxuIiArIGJvZHkucmVwbGFjZSgvXG4kLywgIiIpICsgIlxuIiArIGYgKyAiXG5cbiI7CiAgICAgICAgfSBlbHNlIGlmICh0YWcgPT09ICJCTE9DS1FVT1RFIikgewogICAgICAgICAgdmFyIGlubmVyID0gYmxvY2soYykudHJpbSgpLnNwbGl0KCJcbiIpLm1hcChmdW5jdGlvbiAobCkgeyByZXR1cm4gIj4gIiArIGw7IH0pLmpvaW4oIlxuIik7CiAgICAgICAgICBvdXQgKz0gaW5uZXIgKyAiXG5cbiI7CiAgICAgICAgfSBlbHNlIGlmICh0YWcgPT09ICJIUiIpIG91dCArPSAiLS0tXG5cbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiVEFCTEUiKSBvdXQgKz0gdGFibGUoYykgKyAiXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIkJSIikgb3V0ICs9ICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiU1RST05HIiB8fCB0YWcgPT09ICJCIiB8fCB0YWcgPT09ICJFTSIgfHwgdGFnID09PSAiSSIgfHwKICAgICAgICAgICAgICAgICB0YWcgPT09ICJBIiB8fCB0YWcgPT09ICJDT0RFIiB8fCB0YWcgPT09ICJERUwiIHx8IHRhZyA9PT0gIlMiKQogICAgICAgICAgb3V0ICs9IGlubGluZShjKSArICJcblxuIjsKICAgICAgICBlbHNlIG91dCArPSBibG9jayhjKTsgLy8gdW5rbm93biB3cmFwcGVyOiByZWN1cnNlIChkcm9wIHRhZywga2VlcCBjb250ZW50KQogICAgICB9CiAgICAgIHJldHVybiBvdXQ7CiAgICB9CiAgICAvLyBibG9jaygpIGRpc3BhdGNoZXMgb24gZWFjaCBDSElMRCdzIHRhZywgdHJlYXRpbmcgdGhlIHBhc3NlZCBub2RlIGFzIGEgcGxhaW4KICAgIC8vIGNvbnRhaW5lci4gV3JhcCByb290IGluIGEgb25lLW9mZiBjb250YWluZXIgc28gcm9vdCdzIE9XTiB0YWcgaXMgZGlzcGF0Y2hlZAogICAgLy8gdG9vOiBjYWxsZXJzIHBhc3MgZWl0aGVyIHRoZSBidWJibGUgY29udGFpbmVyIChpdHMgYmxvY2sgY2hpbGRyZW4gcmVuZGVyKSBvcgogICAgLy8gYSBzaW5nbGUgYmxvY2sgZWxlbWVudCBsaWtlIDxwcmU+Lzx1bD4vPHRhYmxlPiAobm93IGhhbmRsZWQsIG5vdCBmbGF0dGVuZWQpLgogICAgcmV0dXJuIGJsb2NrKHsgY2hpbGROb2RlczogW3Jvb3RdIH0pLnJlcGxhY2UoL1xuezMsfS9nLCAiXG5cbiIpLnRyaW0oKTsKICB9CgogIC8vIC0tLS0gcHVyZSBoZWxwZXJzIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KICBmdW5jdGlvbiBoYXNQcmVmaXgobm9kZSwgcHJlZml4KSB7CiAgICBpZiAobm9kZS5ub2RlVHlwZSAhPT0gMSB8fCB0eXBlb2Ygbm9kZS5jbGFzc05hbWUgIT09ICJzdHJpbmciKSByZXR1cm4gZmFsc2U7CiAgICB2YXIgcGFydHMgPSBub2RlLmNsYXNzTmFtZS5zcGxpdCgvXHMrLyk7CiAgICBmb3IgKHZhciBpID0gMDsgaSA8IHBhcnRzLmxlbmd0aDsgaSsrKSBpZiAocGFydHNbaV0uaW5kZXhPZihwcmVmaXgpID09PSAwKSByZXR1cm4gdHJ1ZTsKICAgIHJldHVybiBmYWxzZTsKICB9CgogIC8vIENsYXNzLXByZWZpeCBob29rcyBmb3Igbm9uLWNvbnRlbnQgY2hyb21lIHRoYXQgcmVuZGVycyAqaW5zaWRlKiBhbiBhc3Npc3RhbnQKICAvLyBidWJibGUgKHZlcmlmaWVkIG9uIDIuMS4xNzA7IFRhc2sgNiByZS1waW5zIHRoZXNlKS4gdG9vbCovdGhpbmtpbmdfIGFyZSB0aGUgdjEKICAvLyBleGNsdXNpb25zOyB1bmtub3duQ29udGVudF8gaXMgdGhlIHJlbmRlcmVyJ3MgZmFsbGJhY2sgZm9yIHVucmVjb2duaXplZCBibG9jawogIC8vIHR5cGVzLCBzbyBzdHJpcHBpbmcgaXQgbWFrZXMgYSAqZnV0dXJlKiBibG9jayB0eXBlIGZhaWwgc2FmZSB0byBleGNsdWRlZCByYXRoZXIKICAvLyB0aGFuIGxlYWtpbmcgIlVuc3VwcG9ydGVkIGNvbnRlbnQiIGludG8gdGhlIGNvcHkuIFJlLXBpbiBpZiBhIHByZWZpeCBtb3Zlcy4KICB2YXIgQ0hST01FX1BSRUZJWEVTID0gWyJ0b29sVXNlXyIsICJ0b29sUmVzdWx0XyIsICJ0b29sUmVmZXJlbmNlXyIsICJ0aGlua2luZ18iLCAidW5rbm93bkNvbnRlbnRfIl07CgogIC8vIFRydWUgZm9yIGFueSBub2RlIHRoYXQgbXVzdCBuZXZlciBhcHBlYXIgaW4gY29waWVkIG91dHB1dDogb3VyIG93biBjb250cm9scywKICAvLyB0aGUgcmF0aW5nIHdpZGdldCAoYGRhdGEtbWVzc2FnZS1yYXRpbmdgICsgaXRzICJUaGFua3MgZm9yIHlvdXIgZmVlZGJhY2siCiAgLy8gdGV4dCksIGFueSBidXR0b24gKGNvcHktY29kZSBjaHJvbWUpLCBhbmQgdGhlIGV4Y2x1ZGVkIGNvbnRlbnQgYmxvY2tzIGFib3ZlLgogIGZ1bmN0aW9uIGlzQ2hyb21lKG5vZGUpIHsKICAgIGlmIChub2RlLm5vZGVUeXBlICE9PSAxKSByZXR1cm4gZmFsc2U7CiAgICBpZiAoKG5vZGUudGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKSA9PT0gIkJVVFRPTiIpIHJldHVybiB0cnVlOwogICAgaWYgKG5vZGUuZ2V0QXR0cmlidXRlICYmIG5vZGUuZ2V0QXR0cmlidXRlKCJkYXRhLW1lc3NhZ2UtcmF0aW5nIikgIT09IG51bGwpIHJldHVybiB0cnVlOwogICAgaWYgKGhhc1ByZWZpeChub2RlLCBDT05UUk9MX1BSRUZJWCkpIHJldHVybiB0cnVlOwogICAgZm9yICh2YXIgaSA9IDA7IGkgPCBDSFJPTUVfUFJFRklYRVMubGVuZ3RoOyBpKyspIGlmIChoYXNQcmVmaXgobm9kZSwgQ0hST01FX1BSRUZJWEVTW2ldKSkgcmV0dXJuIHRydWU7CiAgICByZXR1cm4gZmFsc2U7CiAgfQoKICAvLyBEZWVwLWNsb25lIGBjb250ZW50Tm9kZWAsIHRoZW4gc3RyaXAgZXZlcnkgY2hyb21lIG5vZGUgc28gY29waWVkIG91dHB1dCBpcyB0aGUKICAvLyBtZXNzYWdlJ3MgdGV4dCBjb250ZW50IG9ubHkuIFRoaXMgaXMgYSBDT1JSRUNUTkVTUyBHQVRFLCBub3QgY29zbWV0aWM6IHRoZQogIC8vIGRlZmF1bHQgY29udGVudCBub2RlIGlzIHRoZSB3aG9sZSBidWJibGUgKGFsbCBjb250ZW50LWJsb2NrIHNpYmxpbmdzLCBzbyBtdWx0aS0KICAvLyBibG9jayBhc3Npc3RhbnQgdHVybnMgYXJlIGNhcHR1cmVkKSwgYW5kIHRoaXMgc3RyaXAtbGlzdCBpcyB0aGUgb25seSB0aGluZwogIC8vIGtlZXBpbmcgdGhlIHJhdGluZyB3aWRnZXQgYW5kIHYxLWV4Y2x1ZGVkIGJsb2NrcyBvdXQgb2YgdGhlIGNvcHkuCiAgZnVuY3Rpb24gc2FuaXRpemVDbG9uZShjb250ZW50Tm9kZSkgewogICAgdmFyIGNsb25lID0gY29udGVudE5vZGUuY2xvbmVOb2RlKHRydWUpOwogICAgKGZ1bmN0aW9uIHN0cmlwKG5vZGUpIHsKICAgICAgdmFyIGtpZHMgPSAobm9kZS5jaGlsZE5vZGVzIHx8IFtdKS5zbGljZSgpOwogICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGtpZHMubGVuZ3RoOyBpKyspIHsKICAgICAgICB2YXIgYyA9IGtpZHNbaV07CiAgICAgICAgaWYgKGMubm9kZVR5cGUgPT09IDEgJiYgaXNDaHJvbWUoYykpIHsgbm9kZS5yZW1vdmVDaGlsZChjKTsgY29udGludWU7IH0KICAgICAgICBpZiAoYy5ub2RlVHlwZSA9PT0gMSkgc3RyaXAoYyk7CiAgICAgIH0KICAgIH0pKGNsb25lKTsKICAgIHJldHVybiBjbG9uZTsKICB9CgogIGZ1bmN0aW9uIGNsYXNzaWZ5QnViYmxlKG5vZGUpIHsKICAgIGlmIChub2RlLm5vZGVUeXBlICE9PSAxKSByZXR1cm4gbnVsbDsKICAgIGlmIChoYXNQcmVmaXgobm9kZSwgInVzZXJNZXNzYWdlQ29udGFpbmVyXyIpKSByZXR1cm4gInVzZXIiOwogICAgaWYgKG5vZGUuZ2V0QXR0cmlidXRlICYmIG5vZGUuZ2V0QXR0cmlidXRlKCJkYXRhLXRlc3RpZCIpID09PSAiYXNzaXN0YW50LW1lc3NhZ2UiKSByZXR1cm4gImFzc2lzdGFudCI7CiAgICByZXR1cm4gbnVsbDsKICB9CgogIC8vIEJ1aWxkIHRoZSB3aG9sZS1jb252ZXJzYXRpb24gbWFya2Rvd24gZnJvbSBhbiBvcmRlcmVkIGxpc3Qgb2YgYnViYmxlcy4KICAvLyBgY29udGVudE9mKGJ1YmJsZSlgIHJlc29sdmVzIHRoZSBjb250ZW50IG5vZGUgKGRlZmF1bHQ6IHRoZSBidWJibGUgaXRzZWxmLCBzbwogIC8vIGV2ZXJ5IGNvbnRlbnQgYmxvY2sgaXMgaW5jbHVkZWQ7IHNhbml0aXplQ2xvbmUgZHJvcHMgY2hyb21lKTsgYSBkZWZhdWx0IGlzCiAgLy8gcHJvdmlkZWQgZm9yIHRlc3RzLgogIGZ1bmN0aW9uIGNvbnZlcnNhdGlvblRvTWFya2Rvd24oYnViYmxlcywgY29udGVudE9mKSB7CiAgICBjb250ZW50T2YgPSBjb250ZW50T2YgfHwgZnVuY3Rpb24gKGIpIHsgcmV0dXJuIGI7IH07CiAgICB2YXIgcGFydHMgPSBbXTsKICAgIGZvciAodmFyIGkgPSAwOyBpIDwgYnViYmxlcy5sZW5ndGg7IGkrKykgewogICAgICB2YXIgcm9sZSA9IGNsYXNzaWZ5QnViYmxlKGJ1YmJsZXNbaV0pOwogICAgICBpZiAoIXJvbGUpIGNvbnRpbnVlOwogICAgICB2YXIgY2xlYW4gPSBzYW5pdGl6ZUNsb25lKGNvbnRlbnRPZihidWJibGVzW2ldKSk7CiAgICAgIHZhciBib2R5ID0gcm9sZSA9PT0gImFzc2lzdGFudCIgPyBodG1sVG9NYXJrZG93bihjbGVhbikgOiAoY2xlYW4udGV4dENvbnRlbnQgfHwgIiIpLnRyaW0oKTsKICAgICAgaWYgKCFib2R5KSBjb250aW51ZTsKICAgICAgcGFydHMucHVzaCgocm9sZSA9PT0gInVzZXIiID8gIiMjIFVzZXIiIDogIiMjIEFzc2lzdGFudCIpICsgIlxuXG4iICsgYm9keSk7CiAgICB9CiAgICByZXR1cm4gcGFydHMuam9pbigiXG5cbiIpICsgKHBhcnRzLmxlbmd0aCA/ICJcbiIgOiAiIik7CiAgfQoKICAvLyAtLS0tIGV4cG9ydHMgKG5vZGUgdGVzdHMpIC8gYm9vdCAocmVhbCB3ZWJ2aWV3KSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgaWYgKHR5cGVvZiBkb2N1bWVudCAhPT0gInVuZGVmaW5lZCIpIHsKICAgIGJvb3QoKTsKICB9IGVsc2UgaWYgKHR5cGVvZiBtb2R1bGUgIT09ICJ1bmRlZmluZWQiICYmIG1vZHVsZS5leHBvcnRzKSB7CiAgICBtb2R1bGUuZXhwb3J0cyA9IHsgaHRtbFRvTWFya2Rvd246IGh0bWxUb01hcmtkb3duLCBzYW5pdGl6ZUNsb25lOiBzYW5pdGl6ZUNsb25lLAogICAgICAgICAgICAgICAgICAgICAgIGNsYXNzaWZ5QnViYmxlOiBjbGFzc2lmeUJ1YmJsZSwgY29udmVyc2F0aW9uVG9NYXJrZG93bjogY29udmVyc2F0aW9uVG9NYXJrZG93biB9OwogIH0KCiAgLy8gLS0tLSBsaXZlLXdlYnZpZXcgd2lyaW5nIChydW5zIG9ubHkgd2hlbiBhIGRvY3VtZW50IGV4aXN0cykgLS0tLS0tLS0tLS0tLS0tLQogIGZ1bmN0aW9uIHFzKG5vZGUsIHNlbCkgeyB0cnkgeyByZXR1cm4gc2VsICYmIG5vZGUucXVlcnlTZWxlY3RvciA/IG5vZGUucXVlcnlTZWxlY3RvcihzZWwpIDogbnVsbDsgfSBjYXRjaCAoXykgeyByZXR1cm4gbnVsbDsgfSB9CiAgZnVuY3Rpb24gcXNhKHNlbCkgeyB0cnkgeyByZXR1cm4gQXJyYXkucHJvdG90eXBlLnNsaWNlLmNhbGwoZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbChzZWwpKTsgfSBjYXRjaCAoXykgeyByZXR1cm4gW107IH0gfQoKICAvLyBUaGUgY29udGVudCBub2RlIHRvIGNvbnZlcnQvY29weTogdGhlIG9wdGlvbmFsIEFTU0lTVEFOVF9DT05URU5UIHdyYXBwZXIgaWYKICAvLyBwaW5uZWQgYW5kIHByZXNlbnQsIGVsc2UgdGhlIGJ1YmJsZSBpdHNlbGYuIFRoZSBidWJibGUgYWxyZWFkeSBjb250YWlucyBldmVyeQogIC8vIGNvbnRlbnQtYmxvY2sgc2libGluZyBvZiBhIG11bHRpLWJsb2NrIHR1cm4sIGFuZCBzYW5pdGl6ZUNsb25lIHN0cmlwcyB0aGUKICAvLyBjaHJvbWUgKHJhdGluZyB3aWRnZXQsIHRvb2wvdGhpbmtpbmcvdW5rbm93biBibG9ja3MsIGJ1dHRvbnMsIG91ciBjb250cm9scykKICAvLyBlaXRoZXIgd2F5IC0tIHNvIHRoaXMgaXMgYSBuYXJyb3dpbmcsIG5ldmVyIHRoZSB0aGluZyB0aGF0IGd1YXJhbnRlZXMKICAvLyBjb3JyZWN0bmVzcy4KICBmdW5jdGlvbiBjb250ZW50Tm9kZU9mKGJ1YmJsZSwgcm9sZSkgewogICAgaWYgKHJvbGUgPT09ICJhc3Npc3RhbnQiICYmIEFTU0lTVEFOVF9DT05URU5UKSB7CiAgICAgIHZhciBuID0gcXMoYnViYmxlLCBBU1NJU1RBTlRfQ09OVEVOVCk7CiAgICAgIGlmIChuKSByZXR1cm4gbjsKICAgIH0KICAgIHJldHVybiBidWJibGU7CiAgfQoKICBmdW5jdGlvbiBjb3B5VGV4dCh0ZXh0KSB7CiAgICB0cnkgewogICAgICBpZiAobmF2aWdhdG9yLmNsaXBib2FyZCAmJiBuYXZpZ2F0b3IuY2xpcGJvYXJkLndyaXRlVGV4dCkgcmV0dXJuIG5hdmlnYXRvci5jbGlwYm9hcmQud3JpdGVUZXh0KHRleHQpOwogICAgfSBjYXRjaCAoXykge30KICAgIHJldHVybiBQcm9taXNlLnJlc29sdmUoKTsgLy8gYmVzdC1lZmZvcnQ7IG5ldmVyIHRocm93IGludG8gdGhlIGFwcAogIH0KCiAgZnVuY3Rpb24gZmxhc2hGZWVkYmFjayhob3N0KSB7CiAgICB0cnkgewogICAgICB2YXIgZmIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzcGFuIik7CiAgICAgIGZiLmNsYXNzTmFtZSA9IENPTlRST0xfUFJFRklYICsgIi1mZWVkYmFjayI7CiAgICAgIGZiLnRleHRDb250ZW50ID0gIkNvcGllZCI7CiAgICAgIGhvc3QuYXBwZW5kQ2hpbGQoZmIpOwogICAgICBzZXRUaW1lb3V0KGZ1bmN0aW9uICgpIHsgaWYgKGZiICYmIGZiLnBhcmVudE5vZGUpIGZiLnBhcmVudE5vZGUucmVtb3ZlQ2hpbGQoZmIpOyB9LCBGRUVEQkFDS19NUyk7CiAgICB9IGNhdGNoIChfKSB7fQogIH0KCiAgZnVuY3Rpb24gYnViYmxlTWFya2Rvd24oYnViYmxlLCByb2xlKSB7CiAgICB2YXIgY2xlYW4gPSBzYW5pdGl6ZUNsb25lKGNvbnRlbnROb2RlT2YoYnViYmxlLCByb2xlKSk7CiAgICByZXR1cm4gcm9sZSA9PT0gImFzc2lzdGFudCIgPyBodG1sVG9NYXJrZG93bihjbGVhbikgOiAoY2xlYW4udGV4dENvbnRlbnQgfHwgIiIpLnRyaW0oKTsKICB9CiAgZnVuY3Rpb24gYnViYmxlUGxhaW4oYnViYmxlLCByb2xlKSB7CiAgICByZXR1cm4gKHNhbml0aXplQ2xvbmUoY29udGVudE5vZGVPZihidWJibGUsIHJvbGUpKS50ZXh0Q29udGVudCB8fCAiIikudHJpbSgpOwogIH0KCiAgLy8gQnVpbGQgYSBzaW5nbGUgY29udHJvbDogYSBwcmltYXJ5ICJDb3B5IiAobWFya2Rvd24pIHBsdXMgYSBzbWFsbCBjYXJldCB0aGF0CiAgLy8gdG9nZ2xlcyBhIG1lbnUgd2l0aCAiQ29weSBhcyBwbGFpbiB0ZXh0Ii4gQWxsIG5vZGVzIGNhcnJ5IHRoZSBDT05UUk9MX1BSRUZJWAogIC8vIGNsYXNzIHNvIHNhbml0aXplQ2xvbmUgcmVtb3ZlcyB0aGVtIGZyb20gYW55IGNvcGllZCBjb250ZW50LgogIGZ1bmN0aW9uIGJ1aWxkQ29udHJvbChvbk1hcmtkb3duLCBvblBsYWluKSB7CiAgICB2YXIgd3JhcCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInNwYW4iKTsKICAgIHdyYXAuY2xhc3NOYW1lID0gQ09OVFJPTF9QUkVGSVg7CiAgICB2YXIgcHJpbWFyeSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImJ1dHRvbiIpOwogICAgcHJpbWFyeS50eXBlID0gImJ1dHRvbiI7CiAgICBwcmltYXJ5LmNsYXNzTmFtZSA9IENPTlRST0xfUFJFRklYICsgIi1idG4iOwogICAgcHJpbWFyeS50aXRsZSA9ICJDb3B5IGFzIE1hcmtkb3duIjsKICAgIHByaW1hcnkudGV4dENvbnRlbnQgPSAiQ29weSI7CiAgICBwcmltYXJ5LmFkZEV2ZW50TGlzdGVuZXIoImNsaWNrIiwgZnVuY3Rpb24gKGUpIHsgZS5zdG9wUHJvcGFnYXRpb24oKTsgb25NYXJrZG93bihwcmltYXJ5KTsgfSk7CiAgICB2YXIgY2FyZXQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJidXR0b24iKTsKICAgIGNhcmV0LnR5cGUgPSAiYnV0dG9uIjsKICAgIGNhcmV0LmNsYXNzTmFtZSA9IENPTlRST0xfUFJFRklYICsgIi1jYXJldCI7CiAgICBjYXJldC50aXRsZSA9ICJDb3B5IG9wdGlvbnMiOwogICAgY2FyZXQudGV4dENvbnRlbnQgPSAi4pa+IjsgLy8gYmxhY2sgZG93bi1wb2ludGluZyBzbWFsbCB0cmlhbmdsZQogICAgdmFyIG1lbnUgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzcGFuIik7CiAgICBtZW51LmNsYXNzTmFtZSA9IENPTlRST0xfUFJFRklYICsgIi1tZW51IjsKICAgIG1lbnUuc3R5bGUuZGlzcGxheSA9ICJub25lIjsKICAgIHZhciBwbGFpbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImJ1dHRvbiIpOwogICAgcGxhaW4udHlwZSA9ICJidXR0b24iOwogICAgcGxhaW4uY2xhc3NOYW1lID0gQ09OVFJPTF9QUkVGSVggKyAiLWJ0biI7CiAgICBwbGFpbi50ZXh0Q29udGVudCA9ICJDb3B5IGFzIHBsYWluIHRleHQiOwogICAgcGxhaW4uYWRkRXZlbnRMaXN0ZW5lcigiY2xpY2siLCBmdW5jdGlvbiAoZSkgeyBlLnN0b3BQcm9wYWdhdGlvbigpOyBtZW51LnN0eWxlLmRpc3BsYXkgPSAibm9uZSI7IG9uUGxhaW4ocGxhaW4pOyB9KTsKICAgIG1lbnUuYXBwZW5kQ2hpbGQocGxhaW4pOwogICAgY2FyZXQuYWRkRXZlbnRMaXN0ZW5lcigiY2xpY2siLCBmdW5jdGlvbiAoZSkgewogICAgICBlLnN0b3BQcm9wYWdhdGlvbigpOwogICAgICBtZW51LnN0eWxlLmRpc3BsYXkgPSBtZW51LnN0eWxlLmRpc3BsYXkgPT09ICJub25lIiA/ICJpbmxpbmUtYmxvY2siIDogIm5vbmUiOwogICAgfSk7CiAgICB3cmFwLmFwcGVuZENoaWxkKHByaW1hcnkpOwogICAgd3JhcC5hcHBlbmRDaGlsZChjYXJldCk7CiAgICB3cmFwLmFwcGVuZENoaWxkKG1lbnUpOwogICAgcmV0dXJuIHdyYXA7CiAgfQoKICBmdW5jdGlvbiBkZWNvcmF0ZShidWJibGUpIHsKICAgIHRyeSB7CiAgICAgIHZhciByb2xlID0gY2xhc3NpZnlCdWJibGUoYnViYmxlKTsKICAgICAgaWYgKCFyb2xlKSByZXR1cm47CiAgICAgIGlmIChxcyhidWJibGUsICIuIiArIENPTlRST0xfUFJFRklYKSkgcmV0dXJuOyAvLyBhbHJlYWR5IGRlY29yYXRlZAogICAgICB2YXIgY29udHJvbCA9IGJ1aWxkQ29udHJvbCgKICAgICAgICBmdW5jdGlvbiAoaG9zdCkgeyBjb3B5VGV4dChidWJibGVNYXJrZG93bihidWJibGUsIHJvbGUpKS50aGVuKGZ1bmN0aW9uICgpIHsgZmxhc2hGZWVkYmFjayhjb250cm9sKTsgfSk7IH0sCiAgICAgICAgZnVuY3Rpb24gKGhvc3QpIHsgY29weVRleHQoYnViYmxlUGxhaW4oYnViYmxlLCByb2xlKSkudGhlbihmdW5jdGlvbiAoKSB7IGZsYXNoRmVlZGJhY2soY29udHJvbCk7IH0pOyB9CiAgICAgICk7CiAgICAgIGJ1YmJsZS5hcHBlbmRDaGlsZChjb250cm9sKTsKICAgIH0gY2F0Y2ggKF8pIHt9CiAgfQoKICBmdW5jdGlvbiBjb3B5Q29udmVyc2F0aW9uKGZvcm1hdCkgewogICAgdmFyIGJ1YmJsZXMgPSBxc2EoVVNFUl9CVUJCTEUgKyAiLCIgKyBBU1NJU1RBTlRfQlVCQkxFKTsKICAgIGlmIChmb3JtYXQgPT09ICJ0ZXh0IikgewogICAgICB2YXIgbGluZXMgPSBbXTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBidWJibGVzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgdmFyIHJvbGUgPSBjbGFzc2lmeUJ1YmJsZShidWJibGVzW2ldKTsKICAgICAgICBpZiAoIXJvbGUpIGNvbnRpbnVlOwogICAgICAgIHZhciBib2R5ID0gYnViYmxlUGxhaW4oYnViYmxlc1tpXSwgcm9sZSk7CiAgICAgICAgaWYgKGJvZHkpIGxpbmVzLnB1c2goYm9keSk7CiAgICAgIH0KICAgICAgcmV0dXJuIGNvcHlUZXh0KGxpbmVzLmpvaW4oIlxuXG4iKSArIChsaW5lcy5sZW5ndGggPyAiXG4iIDogIiIpKTsKICAgIH0KICAgIHJldHVybiBjb3B5VGV4dChjb252ZXJzYXRpb25Ub01hcmtkb3duKGJ1YmJsZXMsIGZ1bmN0aW9uIChiKSB7CiAgICAgIHJldHVybiBjb250ZW50Tm9kZU9mKGIsIGNsYXNzaWZ5QnViYmxlKGIpKTsKICAgIH0pKTsKICB9CgogIGZ1bmN0aW9uIGluc3RhbGxDb252ZXJzYXRpb25Db250cm9sKCkgewogICAgdHJ5IHsKICAgICAgaWYgKHFzKGRvY3VtZW50LCAiLiIgKyBDT05UUk9MX1BSRUZJWCArICItY29udmVyc2F0aW9uIikpIHJldHVybjsKICAgICAgdmFyIGJhciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpOwogICAgICBiYXIuY2xhc3NOYW1lID0gQ09OVFJPTF9QUkVGSVggKyAiLWNvbnZlcnNhdGlvbiI7CiAgICAgIHZhciBjb250cm9sID0gYnVpbGRDb250cm9sKAogICAgICAgIGZ1bmN0aW9uICgpIHsgY29weUNvbnZlcnNhdGlvbigibWFya2Rvd24iKS50aGVuKGZ1bmN0aW9uICgpIHsgZmxhc2hGZWVkYmFjayhiYXIpOyB9KTsgfSwKICAgICAgICBmdW5jdGlvbiAoKSB7IGNvcHlDb252ZXJzYXRpb24oInRleHQiKS50aGVuKGZ1bmN0aW9uICgpIHsgZmxhc2hGZWVkYmFjayhiYXIpOyB9KTsgfQogICAgICApOwogICAgICBjb250cm9sLnRpdGxlID0gIkNvcHkgZW50aXJlIGNvbnZlcnNhdGlvbiI7CiAgICAgIGJhci5hcHBlbmRDaGlsZChjb250cm9sKTsKICAgICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChiYXIpOyAvLyBmaXhlZC1wb3NpdGlvbiB2aWEgQ1NTOyBwbGFjZW1lbnQgcmVmaW5lZCBpbiBUYXNrIDYKICAgIH0gY2F0Y2ggKF8pIHt9CiAgfQoKICBmdW5jdGlvbiBzd2VlcCgpIHsgdmFyIGIgPSBxc2EoVVNFUl9CVUJCTEUgKyAiLCIgKyBBU1NJU1RBTlRfQlVCQkxFKTsgZm9yICh2YXIgaSA9IDA7IGkgPCBiLmxlbmd0aDsgaSsrKSBkZWNvcmF0ZShiW2ldKTsgfQoKICBmdW5jdGlvbiBib290KCkgewogICAgdHJ5IHsKICAgICAgdmFyIHRhcmdldCA9IChNRVNTQUdFU19DT05UQUlORVIgJiYgcXMoZG9jdW1lbnQsIE1FU1NBR0VTX0NPTlRBSU5FUikpIHx8IGRvY3VtZW50LmJvZHk7CiAgICAgIHN3ZWVwKCk7CiAgICAgIGluc3RhbGxDb252ZXJzYXRpb25Db250cm9sKCk7CiAgICAgIGlmICh0eXBlb2YgTXV0YXRpb25PYnNlcnZlciA9PT0gInVuZGVmaW5lZCIpIHJldHVybjsKICAgICAgdmFyIG9icyA9IG5ldyBNdXRhdGlvbk9ic2VydmVyKGZ1bmN0aW9uICgpIHsgc3dlZXAoKTsgfSk7CiAgICAgIG9icy5vYnNlcnZlKHRhcmdldCwgeyBjaGlsZExpc3Q6IHRydWUsIHN1YnRyZWU6IHRydWUgfSk7CiAgICB9IGNhdGNoIChfKSB7fQogIH0KfSkoKTsK").decode("utf-8") +INJECT_JS = base64.b64decode("LyogY2MtbWQtY29weTogcGVyLW1lc3NhZ2UgYW5kIHdob2xlLWNvbnZlcnNhdGlvbiBjb3B5IChtYXJrZG93bi9wbGFpbikgZm9yIHRoZQogKiBDbGF1ZGUgQ29kZSBWUyBDb2RlIHdlYnZpZXcuIFNlbGYtY29udGFpbmVkIElJRkUgYXBwZW5kZWQgdG8gd2Vidmlldy9pbmRleC5qcy4KICogQWRkaXRpdmUgYW5kIHJlYWQtb25seSB3LnIudC4gYXBwIHN0YXRlOyBrZXllZCBvbiBzdGFibGUgQ1NTLW1vZHVsZSBjbGFzcwogKiBwcmVmaXhlcywgc28gaXQgZmFpbHMgc2FmZSAoY29udHJvbHMgc2ltcGx5IGRvIG5vdCBhcHBlYXIpIGlmIGEgcHJlZml4IG1vdmVzLgogKiBFeHBvc2VzIGl0cyBwdXJlIGZ1bmN0aW9ucyBmb3Igbm9kZSB1bml0IHRlc3RzOyBib290KClzIG9ubHkgaW4gYSByZWFsIHdlYnZpZXcuICovCi8qIExlYWRpbmcgJzsnIHNvIHRoYXQsIGFwcGVuZGVkIGFmdGVyIHRoZSBidW5kbGUsIHRoaXMgSUlGRSBjYW4gbmV2ZXIgYmUgcGFyc2VkIGFzCiAqIGEgY2FsbCBvbiB0aGUgYnVuZGxlJ3MgZmluYWwgZXhwcmVzc2lvbiBpZiBpdCBsYWNrcyBhIHRyYWlsaW5nIHNlbWljb2xvbiAoQVNJCiAqIHNhZmV0eSBhY3Jvc3MgZXh0ZW5zaW9uIGJ1aWxkcykuICovCjsoZnVuY3Rpb24gKCkgewogICJ1c2Ugc3RyaWN0IjsKCiAgdmFyIENPTlRST0xfUFJFRklYID0gImNjLW1kLWNvcHkiOyAvLyBldmVyeSBpbmplY3RlZCBub2RlJ3MgY2xhc3Mgc3RhcnRzIHdpdGggdGhpcwogIHZhciBVU0VSX0JVQkJMRSA9ICdbY2xhc3MqPSJ1c2VyTWVzc2FnZUNvbnRhaW5lcl8iXSc7CiAgLy8gQXNzaXN0YW50IG1lc3NhZ2Ugd3JhcHBlci4gVmVyaWZpZWQgb24gMi4xLjE3MDogdGhlIHJlbmRlciBlbWl0cyBleGFjdGx5IG9uZQogIC8vIGBkYXRhLXRlc3RpZD0iYXNzaXN0YW50LW1lc3NhZ2UiYCBkaXYgcGVyIGFzc2lzdGFudCB0dXJuLCB3aXRoIHRoZSByYXRpbmcKICAvLyB3aWRnZXQgYW5kIGNvbnRlbnQgYmxvY2tzIGFzIGl0cyBjaGlsZHJlbi4gKFRoZSBlYXJsaWVyIGBbZGF0YS1tZXNzYWdlLXJhdGluZ11gCiAgLy8gd2FzIFdST05HOiB0aGF0IGF0dHJpYnV0ZSBzaXRzIG9uIHRoZSBuZXN0ZWQgcmF0aW5nIGNvbnRyb2wsIHdoaWNoIGlzIGFsc28gb25seQogIC8vIHJlbmRlcmVkIGJlaGluZCBhbiBleHBlcmltZW50K2FuYWx5dGljcyBnYXRlLikgUmUtcGlubmVkIGluIFRhc2sgNi4KICB2YXIgQVNTSVNUQU5UX0JVQkJMRSA9ICdbZGF0YS10ZXN0aWQ9ImFzc2lzdGFudC1tZXNzYWdlIl0nOwogIHZhciBNRVNTQUdFU19DT05UQUlORVIgPSAnW2NsYXNzKj0ibWVzc2FnZXNDb250YWluZXJfIl0nOyAvLyBlLmcuICdbY2xhc3MqPSJ0aW1lbGluZV8iXSc7ICIiIC0+IG9ic2VydmUgZG9jdW1lbnQuYm9keQogIC8vIE9wdGlvbmFsIG5hcnJvd2luZyBvbmx5LiBNVVNUIGJlIGEgc2luZ2xlIHdyYXBwZXIgYXJvdW5kIEFMTCBjb250ZW50IGJsb2NrcywKICAvLyBub3QgYSBwZXItYmxvY2sgY2xhc3MgKGEgdHVybiBoYXMgbXVsdGlwbGUgYmxvY2tzKS4gIiIgLT4gdXNlIHRoZSBidWJibGUgaXRzZWxmCiAgLy8gKGFscmVhZHkgYWdncmVnYXRlcyBhbGwgYmxvY2tzOyBzYW5pdGl6ZUNsb25lIGlzIHRoZSBjb3JyZWN0bmVzcyBnYXRlKS4KICB2YXIgQVNTSVNUQU5UX0NPTlRFTlQgPSAiIjsKICB2YXIgRkVFREJBQ0tfTVMgPSAxODAwOwoKICAvLyAtLS0tIEhUTUwgLT4gTWFya2Rvd24gKERPTSB3YWxrKSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgLy8gVXNlcyBvbmx5OiBub2RlVHlwZSwgdGFnTmFtZSwgY2hpbGROb2RlcywgdGV4dENvbnRlbnQsIGdldEF0dHJpYnV0ZSwgY2xhc3NOYW1lLgogIGZ1bmN0aW9uIGh0bWxUb01hcmtkb3duKHJvb3QpIHsKICAgIC8vIExvbmdlc3QgcnVuIG9mIGNvbnNlY3V0aXZlIGJhY2t0aWNrcyBpbiBzLCBzbyBhIGNvZGUgZGVsaW1pdGVyL2ZlbmNlIGNhbiBiZQogICAgLy8gY2hvc2VuIGxvbmdlciB0aGFuIGFueXRoaW5nIGluc2lkZSBpdCAoZWxzZSBgYGAgaW4gdGhlIGNvbnRlbnQgY2xvc2VzIGVhcmx5KS4KICAgIGZ1bmN0aW9uIGJhY2t0aWNrUnVuKHMpIHsKICAgICAgdmFyIG1heCA9IDAsIGN1ciA9IDA7CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgcy5sZW5ndGg7IGkrKykgewogICAgICAgIGlmIChzLmNoYXJBdChpKSA9PT0gImAiKSB7IGN1cisrOyBpZiAoY3VyID4gbWF4KSBtYXggPSBjdXI7IH0gZWxzZSBjdXIgPSAwOwogICAgICB9CiAgICAgIHJldHVybiBtYXg7CiAgICB9CiAgICBmdW5jdGlvbiBmZW5jZShzLCBtaW4pIHsgdmFyIG4gPSBiYWNrdGlja1J1bihzKSArIDE7IGlmIChuIDwgbWluKSBuID0gbWluOyByZXR1cm4gbmV3IEFycmF5KG4gKyAxKS5qb2luKCJgIik7IH0KICAgIGZ1bmN0aW9uIGlubGluZShub2RlKSB7CiAgICAgIHZhciBvdXQgPSAiIjsKICAgICAgdmFyIGtpZHMgPSBub2RlLmNoaWxkTm9kZXMgfHwgW107CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwga2lkcy5sZW5ndGg7IGkrKykgewogICAgICAgIHZhciBjID0ga2lkc1tpXTsKICAgICAgICBpZiAoYy5ub2RlVHlwZSA9PT0gMykgeyBvdXQgKz0gYy50ZXh0Q29udGVudCB8fCAiIjsgY29udGludWU7IH0KICAgICAgICBpZiAoYy5ub2RlVHlwZSAhPT0gMSkgY29udGludWU7CiAgICAgICAgdmFyIHRhZyA9IChjLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCk7CiAgICAgICAgaWYgKHRhZyA9PT0gIkJSIikgb3V0ICs9ICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiU1RST05HIiB8fCB0YWcgPT09ICJCIikgb3V0ICs9ICIqKiIgKyBpbmxpbmUoYykgKyAiKioiOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIkVNIiB8fCB0YWcgPT09ICJJIikgb3V0ICs9ICIqIiArIGlubGluZShjKSArICIqIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJERUwiIHx8IHRhZyA9PT0gIlMiKSBvdXQgKz0gIn5+IiArIGlubGluZShjKSArICJ+fiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiQ09ERSIpIHsKICAgICAgICAgIHZhciBjdCA9IGMudGV4dENvbnRlbnQgfHwgIiI7CiAgICAgICAgICB2YXIgZCA9IGZlbmNlKGN0LCAxKTsKICAgICAgICAgIC8vIENvbW1vbk1hcmsgc3RyaXBzIG9uZSBsZWFkaW5nK3RyYWlsaW5nIHNwYWNlLCBzbyBwYWQgd2hlbiBhbiBlZGdlIGlzIGEKICAgICAgICAgIC8vIGJhY2t0aWNrIHRvIGtlZXAgaXQgZnJvbSBtZXJnaW5nIHdpdGggdGhlIGRlbGltaXRlci4KICAgICAgICAgIHZhciBwID0gKGN0LmNoYXJBdCgwKSA9PT0gImAiIHx8IGN0LmNoYXJBdChjdC5sZW5ndGggLSAxKSA9PT0gImAiKSA/ICIgIiA6ICIiOwogICAgICAgICAgb3V0ICs9IGQgKyBwICsgY3QgKyBwICsgZDsKICAgICAgICB9CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiQSIpIHsKICAgICAgICAgIHZhciBocmVmID0gYy5nZXRBdHRyaWJ1dGUgPyBjLmdldEF0dHJpYnV0ZSgiaHJlZiIpIDogbnVsbDsKICAgICAgICAgIHZhciB0ID0gaW5saW5lKGMpOwogICAgICAgICAgb3V0ICs9IGhyZWYgPyAiWyIgKyB0ICsgIl0oIiArIGhyZWYgKyAiKSIgOiB0OwogICAgICAgIH0gZWxzZSBvdXQgKz0gaW5saW5lKGMpOyAvLyB1bmtub3duIGlubGluZSB3cmFwcGVyOiBrZWVwIHRleHQsIGRyb3AgdGFnCiAgICAgIH0KICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIGZ1bmN0aW9uIGxhbmdPZihjb2RlRWwpIHsKICAgICAgdmFyIGNscyA9ICIiOwogICAgICBpZiAoY29kZUVsKSBjbHMgPSAoY29kZUVsLmdldEF0dHJpYnV0ZSAmJiBjb2RlRWwuZ2V0QXR0cmlidXRlKCJjbGFzcyIpKSB8fCBjb2RlRWwuY2xhc3NOYW1lIHx8ICIiOwogICAgICB2YXIgbSA9IC9sYW5ndWFnZS0oW0EtWmEtejAtOSsjLlwtXSspLy5leGVjKGNscyB8fCAiIik7CiAgICAgIHJldHVybiBtID8gbVsxXSA6ICIiOwogICAgfQogICAgZnVuY3Rpb24gZmluZENoaWxkVGFnKG5vZGUsIHRhZykgewogICAgICB2YXIga2lkcyA9IG5vZGUuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgaWYgKGtpZHNbaV0ubm9kZVR5cGUgPT09IDEgJiYgKGtpZHNbaV0udGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKSA9PT0gdGFnKSByZXR1cm4ga2lkc1tpXTsKICAgICAgfQogICAgICByZXR1cm4gbnVsbDsKICAgIH0KICAgIGZ1bmN0aW9uIGxpc3Qobm9kZSwgb3JkZXJlZCwgZGVwdGgpIHsKICAgICAgdmFyIG91dCA9ICIiLCBuID0gMTsKICAgICAgdmFyIGtpZHMgPSBub2RlLmNoaWxkTm9kZXMgfHwgW107CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwga2lkcy5sZW5ndGg7IGkrKykgewogICAgICAgIHZhciBsaSA9IGtpZHNbaV07CiAgICAgICAgaWYgKGxpLm5vZGVUeXBlICE9PSAxIHx8IChsaS50YWdOYW1lIHx8ICIiKS50b1VwcGVyQ2FzZSgpICE9PSAiTEkiKSBjb250aW51ZTsKICAgICAgICB2YXIgbWFya2VyID0gb3JkZXJlZCA/IG4rKyArICIuICIgOiAiLSAiOwogICAgICAgIHZhciBpbmRlbnQgPSBuZXcgQXJyYXkoZGVwdGggKyAxKS5qb2luKCIgICIpOwogICAgICAgIHZhciBsZWFkID0gIiIsIG5lc3RlZCA9ICIiOwogICAgICAgIHZhciBsayA9IGxpLmNoaWxkTm9kZXMgfHwgW107CiAgICAgICAgZm9yICh2YXIgaiA9IDA7IGogPCBsay5sZW5ndGg7IGorKykgewogICAgICAgICAgdmFyIGNoID0gbGtbal07CiAgICAgICAgICB2YXIgY3QgPSBjaC5ub2RlVHlwZSA9PT0gMSA/IChjaC50YWdOYW1lIHx8ICIiKS50b1VwcGVyQ2FzZSgpIDogIiI7CiAgICAgICAgICBpZiAoY3QgPT09ICJVTCIpIG5lc3RlZCArPSBsaXN0KGNoLCBmYWxzZSwgZGVwdGggKyAxKTsKICAgICAgICAgIGVsc2UgaWYgKGN0ID09PSAiT0wiKSBuZXN0ZWQgKz0gbGlzdChjaCwgdHJ1ZSwgZGVwdGggKyAxKTsKICAgICAgICAgIGVsc2UgaWYgKGNoLm5vZGVUeXBlID09PSAzKSBsZWFkICs9IGNoLnRleHRDb250ZW50IHx8ICIiOwogICAgICAgICAgZWxzZSBsZWFkICs9IGlubGluZShjaCk7CiAgICAgICAgfQogICAgICAgIG91dCArPSBpbmRlbnQgKyBtYXJrZXIgKyBsZWFkLnRyaW0oKSArICJcbiIgKyBuZXN0ZWQ7CiAgICAgIH0KICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIGZ1bmN0aW9uIHRhYmxlKG5vZGUpIHsKICAgICAgdmFyIHJvd3MgPSBbXTsKICAgICAgKGZ1bmN0aW9uIGNvbGxlY3QoY29udGFpbmVyKSB7CiAgICAgICAgdmFyIGtpZHMgPSBjb250YWluZXIuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGtpZHMubGVuZ3RoOyBpKyspIHsKICAgICAgICAgIHZhciBjID0ga2lkc1tpXTsKICAgICAgICAgIGlmIChjLm5vZGVUeXBlICE9PSAxKSBjb250aW51ZTsKICAgICAgICAgIHZhciB0ID0gKGMudGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKTsKICAgICAgICAgIGlmICh0ID09PSAiVEhFQUQiIHx8IHQgPT09ICJUQk9EWSIgfHwgdCA9PT0gIlRGT09UIikgY29sbGVjdChjKTsKICAgICAgICAgIGVsc2UgaWYgKHQgPT09ICJUUiIpIHsKICAgICAgICAgICAgdmFyIGNlbGxzID0gW10sIGNjID0gYy5jaGlsZE5vZGVzIHx8IFtdOwogICAgICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IGNjLmxlbmd0aDsgaisrKSB7CiAgICAgICAgICAgICAgdmFyIGQgPSBjY1tqXTsKICAgICAgICAgICAgICBpZiAoZC5ub2RlVHlwZSAhPT0gMSkgY29udGludWU7CiAgICAgICAgICAgICAgdmFyIGR0ID0gKGQudGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKTsKICAgICAgICAgICAgICBpZiAoZHQgPT09ICJUSCIgfHwgZHQgPT09ICJURCIpIGNlbGxzLnB1c2goaW5saW5lKGQpLnRyaW0oKSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgcm93cy5wdXNoKGNlbGxzKTsKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0pKG5vZGUpOwogICAgICBpZiAoIXJvd3MubGVuZ3RoKSByZXR1cm4gIiI7CiAgICAgIHZhciBoZWFkID0gcm93c1swXSwgYm9keSA9IHJvd3Muc2xpY2UoMSk7CiAgICAgIHZhciBzZXAgPSBoZWFkLm1hcChmdW5jdGlvbiAoKSB7IHJldHVybiAiLS0tIjsgfSk7CiAgICAgIHZhciBvdXQgPSAifCAiICsgaGVhZC5qb2luKCIgfCAiKSArICIgfFxufCAiICsgc2VwLmpvaW4oIiB8ICIpICsgIiB8XG4iOwogICAgICBmb3IgKHZhciBrID0gMDsgayA8IGJvZHkubGVuZ3RoOyBrKyspIG91dCArPSAifCAiICsgYm9keVtrXS5qb2luKCIgfCAiKSArICIgfFxuIjsKICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIGZ1bmN0aW9uIGJsb2NrKG5vZGUpIHsKICAgICAgdmFyIG91dCA9ICIiOwogICAgICB2YXIga2lkcyA9IG5vZGUuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgdmFyIGMgPSBraWRzW2ldOwogICAgICAgIGlmIChjLm5vZGVUeXBlID09PSAzKSB7IGlmICgoYy50ZXh0Q29udGVudCB8fCAiIikudHJpbSgpKSBvdXQgKz0gYy50ZXh0Q29udGVudDsgY29udGludWU7IH0KICAgICAgICBpZiAoYy5ub2RlVHlwZSAhPT0gMSkgY29udGludWU7CiAgICAgICAgdmFyIHRhZyA9IChjLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCk7CiAgICAgICAgaWYgKC9eSFsxLTZdJC8udGVzdCh0YWcpKSBvdXQgKz0gbmV3IEFycmF5KCt0YWdbMV0gKyAxKS5qb2luKCIjIikgKyAiICIgKyBpbmxpbmUoYykudHJpbSgpICsgIlxuXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIlAiKSBvdXQgKz0gaW5saW5lKGMpLnRyaW0oKSArICJcblxuIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJVTCIpIG91dCArPSBsaXN0KGMsIGZhbHNlLCAwKSArICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiT0wiKSBvdXQgKz0gbGlzdChjLCB0cnVlLCAwKSArICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiUFJFIikgewogICAgICAgICAgdmFyIGNvZGUgPSBmaW5kQ2hpbGRUYWcoYywgIkNPREUiKTsKICAgICAgICAgIHZhciBsYW5nID0gbGFuZ09mKGNvZGUgfHwgYyk7CiAgICAgICAgICB2YXIgYm9keSA9IChjb2RlIHx8IGMpLnRleHRDb250ZW50IHx8ICIiOwogICAgICAgICAgdmFyIGYgPSBmZW5jZShib2R5LCAzKTsKICAgICAgICAgIG91dCArPSBmICsgbGFuZyArICJcbiIgKyBib2R5LnJlcGxhY2UoL1xuJC8sICIiKSArICJcbiIgKyBmICsgIlxuXG4iOwogICAgICAgIH0gZWxzZSBpZiAodGFnID09PSAiQkxPQ0tRVU9URSIpIHsKICAgICAgICAgIHZhciBpbm5lciA9IGJsb2NrKGMpLnRyaW0oKS5zcGxpdCgiXG4iKS5tYXAoZnVuY3Rpb24gKGwpIHsgcmV0dXJuICI+ICIgKyBsOyB9KS5qb2luKCJcbiIpOwogICAgICAgICAgb3V0ICs9IGlubmVyICsgIlxuXG4iOwogICAgICAgIH0gZWxzZSBpZiAodGFnID09PSAiSFIiKSBvdXQgKz0gIi0tLVxuXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIlRBQkxFIikgb3V0ICs9IHRhYmxlKGMpICsgIlxuIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJCUiIpIG91dCArPSAiXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIlNUUk9ORyIgfHwgdGFnID09PSAiQiIgfHwgdGFnID09PSAiRU0iIHx8IHRhZyA9PT0gIkkiIHx8CiAgICAgICAgICAgICAgICAgdGFnID09PSAiQSIgfHwgdGFnID09PSAiQ09ERSIgfHwgdGFnID09PSAiREVMIiB8fCB0YWcgPT09ICJTIikKICAgICAgICAgIG91dCArPSBpbmxpbmUoYykgKyAiXG5cbiI7CiAgICAgICAgZWxzZSBvdXQgKz0gYmxvY2soYyk7IC8vIHVua25vd24gd3JhcHBlcjogcmVjdXJzZSAoZHJvcCB0YWcsIGtlZXAgY29udGVudCkKICAgICAgfQogICAgICByZXR1cm4gb3V0OwogICAgfQogICAgLy8gYmxvY2soKSBkaXNwYXRjaGVzIG9uIGVhY2ggQ0hJTEQncyB0YWcsIHRyZWF0aW5nIHRoZSBwYXNzZWQgbm9kZSBhcyBhIHBsYWluCiAgICAvLyBjb250YWluZXIuIFdyYXAgcm9vdCBpbiBhIG9uZS1vZmYgY29udGFpbmVyIHNvIHJvb3QncyBPV04gdGFnIGlzIGRpc3BhdGNoZWQKICAgIC8vIHRvbzogY2FsbGVycyBwYXNzIGVpdGhlciB0aGUgYnViYmxlIGNvbnRhaW5lciAoaXRzIGJsb2NrIGNoaWxkcmVuIHJlbmRlcikgb3IKICAgIC8vIGEgc2luZ2xlIGJsb2NrIGVsZW1lbnQgbGlrZSA8cHJlPi88dWw+Lzx0YWJsZT4gKG5vdyBoYW5kbGVkLCBub3QgZmxhdHRlbmVkKS4KICAgIHJldHVybiBibG9jayh7IGNoaWxkTm9kZXM6IFtyb290XSB9KS5yZXBsYWNlKC9cbnszLH0vZywgIlxuXG4iKS50cmltKCk7CiAgfQoKICAvLyAtLS0tIHB1cmUgaGVscGVycyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgZnVuY3Rpb24gaGFzUHJlZml4KG5vZGUsIHByZWZpeCkgewogICAgaWYgKG5vZGUubm9kZVR5cGUgIT09IDEgfHwgdHlwZW9mIG5vZGUuY2xhc3NOYW1lICE9PSAic3RyaW5nIikgcmV0dXJuIGZhbHNlOwogICAgdmFyIHBhcnRzID0gbm9kZS5jbGFzc05hbWUuc3BsaXQoL1xzKy8pOwogICAgZm9yICh2YXIgaSA9IDA7IGkgPCBwYXJ0cy5sZW5ndGg7IGkrKykgaWYgKHBhcnRzW2ldLmluZGV4T2YocHJlZml4KSA9PT0gMCkgcmV0dXJuIHRydWU7CiAgICByZXR1cm4gZmFsc2U7CiAgfQoKICAvLyBDbGFzcy1wcmVmaXggaG9va3MgZm9yIG5vbi1jb250ZW50IGNocm9tZSB0aGF0IHJlbmRlcnMgKmluc2lkZSogYW4gYXNzaXN0YW50CiAgLy8gYnViYmxlICh2ZXJpZmllZCBvbiAyLjEuMTcwOyBUYXNrIDYgcmUtcGlucyB0aGVzZSkuIHRvb2wqL3RoaW5raW5nXyBhcmUgdGhlIHYxCiAgLy8gZXhjbHVzaW9uczsgdW5rbm93bkNvbnRlbnRfIGlzIHRoZSByZW5kZXJlcidzIGZhbGxiYWNrIGZvciB1bnJlY29nbml6ZWQgYmxvY2sKICAvLyB0eXBlcywgc28gc3RyaXBwaW5nIGl0IG1ha2VzIGEgKmZ1dHVyZSogYmxvY2sgdHlwZSBmYWlsIHNhZmUgdG8gZXhjbHVkZWQgcmF0aGVyCiAgLy8gdGhhbiBsZWFraW5nICJVbnN1cHBvcnRlZCBjb250ZW50IiBpbnRvIHRoZSBjb3B5LiBSZS1waW4gaWYgYSBwcmVmaXggbW92ZXMuCiAgdmFyIENIUk9NRV9QUkVGSVhFUyA9IFsidG9vbFVzZV8iLCAidG9vbFJlc3VsdF8iLCAidG9vbFJlZmVyZW5jZV8iLCAidGhpbmtpbmdfIiwgInVua25vd25Db250ZW50XyJdOwoKICAvLyBUcnVlIGZvciBhbnkgbm9kZSB0aGF0IG11c3QgbmV2ZXIgYXBwZWFyIGluIGNvcGllZCBvdXRwdXQ6IG91ciBvd24gY29udHJvbHMsCiAgLy8gdGhlIHJhdGluZyB3aWRnZXQgKGBkYXRhLW1lc3NhZ2UtcmF0aW5nYCArIGl0cyAiVGhhbmtzIGZvciB5b3VyIGZlZWRiYWNrIgogIC8vIHRleHQpLCBhbnkgYnV0dG9uIChjb3B5LWNvZGUgY2hyb21lKSwgYW5kIHRoZSBleGNsdWRlZCBjb250ZW50IGJsb2NrcyBhYm92ZS4KICBmdW5jdGlvbiBpc0Nocm9tZShub2RlKSB7CiAgICBpZiAobm9kZS5ub2RlVHlwZSAhPT0gMSkgcmV0dXJuIGZhbHNlOwogICAgaWYgKChub2RlLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCkgPT09ICJCVVRUT04iKSByZXR1cm4gdHJ1ZTsKICAgIGlmIChub2RlLmdldEF0dHJpYnV0ZSAmJiBub2RlLmdldEF0dHJpYnV0ZSgiZGF0YS1tZXNzYWdlLXJhdGluZyIpICE9PSBudWxsKSByZXR1cm4gdHJ1ZTsKICAgIGlmIChoYXNQcmVmaXgobm9kZSwgQ09OVFJPTF9QUkVGSVgpKSByZXR1cm4gdHJ1ZTsKICAgIGZvciAodmFyIGkgPSAwOyBpIDwgQ0hST01FX1BSRUZJWEVTLmxlbmd0aDsgaSsrKSBpZiAoaGFzUHJlZml4KG5vZGUsIENIUk9NRV9QUkVGSVhFU1tpXSkpIHJldHVybiB0cnVlOwogICAgcmV0dXJuIGZhbHNlOwogIH0KCiAgLy8gRGVlcC1jbG9uZSBgY29udGVudE5vZGVgLCB0aGVuIHN0cmlwIGV2ZXJ5IGNocm9tZSBub2RlIHNvIGNvcGllZCBvdXRwdXQgaXMgdGhlCiAgLy8gbWVzc2FnZSdzIHRleHQgY29udGVudCBvbmx5LiBUaGlzIGlzIGEgQ09SUkVDVE5FU1MgR0FURSwgbm90IGNvc21ldGljOiB0aGUKICAvLyBkZWZhdWx0IGNvbnRlbnQgbm9kZSBpcyB0aGUgd2hvbGUgYnViYmxlIChhbGwgY29udGVudC1ibG9jayBzaWJsaW5ncywgc28gbXVsdGktCiAgLy8gYmxvY2sgYXNzaXN0YW50IHR1cm5zIGFyZSBjYXB0dXJlZCksIGFuZCB0aGlzIHN0cmlwLWxpc3QgaXMgdGhlIG9ubHkgdGhpbmcKICAvLyBrZWVwaW5nIHRoZSByYXRpbmcgd2lkZ2V0IGFuZCB2MS1leGNsdWRlZCBibG9ja3Mgb3V0IG9mIHRoZSBjb3B5LgogIGZ1bmN0aW9uIHNhbml0aXplQ2xvbmUoY29udGVudE5vZGUpIHsKICAgIHZhciBjbG9uZSA9IGNvbnRlbnROb2RlLmNsb25lTm9kZSh0cnVlKTsKICAgIChmdW5jdGlvbiBzdHJpcChub2RlKSB7CiAgICAgIHZhciBraWRzID0gKG5vZGUuY2hpbGROb2RlcyB8fCBbXSkuc2xpY2UoKTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgdmFyIGMgPSBraWRzW2ldOwogICAgICAgIGlmIChjLm5vZGVUeXBlID09PSAxICYmIGlzQ2hyb21lKGMpKSB7IG5vZGUucmVtb3ZlQ2hpbGQoYyk7IGNvbnRpbnVlOyB9CiAgICAgICAgaWYgKGMubm9kZVR5cGUgPT09IDEpIHN0cmlwKGMpOwogICAgICB9CiAgICB9KShjbG9uZSk7CiAgICByZXR1cm4gY2xvbmU7CiAgfQoKICBmdW5jdGlvbiBjbGFzc2lmeUJ1YmJsZShub2RlKSB7CiAgICBpZiAobm9kZS5ub2RlVHlwZSAhPT0gMSkgcmV0dXJuIG51bGw7CiAgICBpZiAoaGFzUHJlZml4KG5vZGUsICJ1c2VyTWVzc2FnZUNvbnRhaW5lcl8iKSkgcmV0dXJuICJ1c2VyIjsKICAgIGlmIChub2RlLmdldEF0dHJpYnV0ZSAmJiBub2RlLmdldEF0dHJpYnV0ZSgiZGF0YS10ZXN0aWQiKSA9PT0gImFzc2lzdGFudC1tZXNzYWdlIikgcmV0dXJuICJhc3Npc3RhbnQiOwogICAgcmV0dXJuIG51bGw7CiAgfQoKICAvLyBCdWlsZCB0aGUgd2hvbGUtY29udmVyc2F0aW9uIG1hcmtkb3duIGZyb20gYW4gb3JkZXJlZCBsaXN0IG9mIGJ1YmJsZXMuCiAgLy8gYGNvbnRlbnRPZihidWJibGUpYCByZXNvbHZlcyB0aGUgY29udGVudCBub2RlIChkZWZhdWx0OiB0aGUgYnViYmxlIGl0c2VsZiwgc28KICAvLyBldmVyeSBjb250ZW50IGJsb2NrIGlzIGluY2x1ZGVkOyBzYW5pdGl6ZUNsb25lIGRyb3BzIGNocm9tZSk7IGEgZGVmYXVsdCBpcwogIC8vIHByb3ZpZGVkIGZvciB0ZXN0cy4KICBmdW5jdGlvbiBjb252ZXJzYXRpb25Ub01hcmtkb3duKGJ1YmJsZXMsIGNvbnRlbnRPZikgewogICAgY29udGVudE9mID0gY29udGVudE9mIHx8IGZ1bmN0aW9uIChiKSB7IHJldHVybiBiOyB9OwogICAgdmFyIHBhcnRzID0gW107CiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGJ1YmJsZXMubGVuZ3RoOyBpKyspIHsKICAgICAgdmFyIHJvbGUgPSBjbGFzc2lmeUJ1YmJsZShidWJibGVzW2ldKTsKICAgICAgaWYgKCFyb2xlKSBjb250aW51ZTsKICAgICAgdmFyIGNsZWFuID0gc2FuaXRpemVDbG9uZShjb250ZW50T2YoYnViYmxlc1tpXSkpOwogICAgICB2YXIgYm9keSA9IHJvbGUgPT09ICJhc3Npc3RhbnQiID8gaHRtbFRvTWFya2Rvd24oY2xlYW4pIDogKGNsZWFuLnRleHRDb250ZW50IHx8ICIiKS50cmltKCk7CiAgICAgIGlmICghYm9keSkgY29udGludWU7CiAgICAgIHBhcnRzLnB1c2goKHJvbGUgPT09ICJ1c2VyIiA/ICIjIyBVc2VyIiA6ICIjIyBBc3Npc3RhbnQiKSArICJcblxuIiArIGJvZHkpOwogICAgfQogICAgcmV0dXJuIHBhcnRzLmpvaW4oIlxuXG4iKSArIChwYXJ0cy5sZW5ndGggPyAiXG4iIDogIiIpOwogIH0KCiAgLy8gLS0tLSBleHBvcnRzIChub2RlIHRlc3RzKSAvIGJvb3QgKHJlYWwgd2VidmlldykgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogIGlmICh0eXBlb2YgZG9jdW1lbnQgIT09ICJ1bmRlZmluZWQiKSB7CiAgICBib290KCk7CiAgfSBlbHNlIGlmICh0eXBlb2YgbW9kdWxlICE9PSAidW5kZWZpbmVkIiAmJiBtb2R1bGUuZXhwb3J0cykgewogICAgbW9kdWxlLmV4cG9ydHMgPSB7IGh0bWxUb01hcmtkb3duOiBodG1sVG9NYXJrZG93biwgc2FuaXRpemVDbG9uZTogc2FuaXRpemVDbG9uZSwKICAgICAgICAgICAgICAgICAgICAgICBjbGFzc2lmeUJ1YmJsZTogY2xhc3NpZnlCdWJibGUsIGNvbnZlcnNhdGlvblRvTWFya2Rvd246IGNvbnZlcnNhdGlvblRvTWFya2Rvd24gfTsKICB9CgogIC8vIC0tLS0gbGl2ZS13ZWJ2aWV3IHdpcmluZyAocnVucyBvbmx5IHdoZW4gYSBkb2N1bWVudCBleGlzdHMpIC0tLS0tLS0tLS0tLS0tLS0KICBmdW5jdGlvbiBxcyhub2RlLCBzZWwpIHsgdHJ5IHsgcmV0dXJuIHNlbCAmJiBub2RlLnF1ZXJ5U2VsZWN0b3IgPyBub2RlLnF1ZXJ5U2VsZWN0b3Ioc2VsKSA6IG51bGw7IH0gY2F0Y2ggKF8pIHsgcmV0dXJuIG51bGw7IH0gfQogIGZ1bmN0aW9uIHFzYShzZWwpIHsgdHJ5IHsgcmV0dXJuIEFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoc2VsKSk7IH0gY2F0Y2ggKF8pIHsgcmV0dXJuIFtdOyB9IH0KCiAgLy8gVGhlIGNvbnRlbnQgbm9kZSB0byBjb252ZXJ0L2NvcHk6IHRoZSBvcHRpb25hbCBBU1NJU1RBTlRfQ09OVEVOVCB3cmFwcGVyIGlmCiAgLy8gcGlubmVkIGFuZCBwcmVzZW50LCBlbHNlIHRoZSBidWJibGUgaXRzZWxmLiBUaGUgYnViYmxlIGFscmVhZHkgY29udGFpbnMgZXZlcnkKICAvLyBjb250ZW50LWJsb2NrIHNpYmxpbmcgb2YgYSBtdWx0aS1ibG9jayB0dXJuLCBhbmQgc2FuaXRpemVDbG9uZSBzdHJpcHMgdGhlCiAgLy8gY2hyb21lIChyYXRpbmcgd2lkZ2V0LCB0b29sL3RoaW5raW5nL3Vua25vd24gYmxvY2tzLCBidXR0b25zLCBvdXIgY29udHJvbHMpCiAgLy8gZWl0aGVyIHdheSAtLSBzbyB0aGlzIGlzIGEgbmFycm93aW5nLCBuZXZlciB0aGUgdGhpbmcgdGhhdCBndWFyYW50ZWVzCiAgLy8gY29ycmVjdG5lc3MuCiAgZnVuY3Rpb24gY29udGVudE5vZGVPZihidWJibGUsIHJvbGUpIHsKICAgIGlmIChyb2xlID09PSAiYXNzaXN0YW50IiAmJiBBU1NJU1RBTlRfQ09OVEVOVCkgewogICAgICB2YXIgbiA9IHFzKGJ1YmJsZSwgQVNTSVNUQU5UX0NPTlRFTlQpOwogICAgICBpZiAobikgcmV0dXJuIG47CiAgICB9CiAgICByZXR1cm4gYnViYmxlOwogIH0KCiAgZnVuY3Rpb24gY29weVRleHQodGV4dCkgewogICAgdHJ5IHsKICAgICAgaWYgKG5hdmlnYXRvci5jbGlwYm9hcmQgJiYgbmF2aWdhdG9yLmNsaXBib2FyZC53cml0ZVRleHQpIHJldHVybiBuYXZpZ2F0b3IuY2xpcGJvYXJkLndyaXRlVGV4dCh0ZXh0KTsKICAgIH0gY2F0Y2ggKF8pIHt9CiAgICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKCk7IC8vIGJlc3QtZWZmb3J0OyBuZXZlciB0aHJvdyBpbnRvIHRoZSBhcHAKICB9CgogIGZ1bmN0aW9uIGZsYXNoRmVlZGJhY2soaG9zdCkgewogICAgdHJ5IHsKICAgICAgdmFyIGZiID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic3BhbiIpOwogICAgICBmYi5jbGFzc05hbWUgPSBDT05UUk9MX1BSRUZJWCArICItZmVlZGJhY2siOwogICAgICBmYi50ZXh0Q29udGVudCA9ICJDb3BpZWQiOwogICAgICBob3N0LmFwcGVuZENoaWxkKGZiKTsKICAgICAgc2V0VGltZW91dChmdW5jdGlvbiAoKSB7IGlmIChmYiAmJiBmYi5wYXJlbnROb2RlKSBmYi5wYXJlbnROb2RlLnJlbW92ZUNoaWxkKGZiKTsgfSwgRkVFREJBQ0tfTVMpOwogICAgfSBjYXRjaCAoXykge30KICB9CgogIGZ1bmN0aW9uIGJ1YmJsZU1hcmtkb3duKGJ1YmJsZSwgcm9sZSkgewogICAgdmFyIGNsZWFuID0gc2FuaXRpemVDbG9uZShjb250ZW50Tm9kZU9mKGJ1YmJsZSwgcm9sZSkpOwogICAgcmV0dXJuIHJvbGUgPT09ICJhc3Npc3RhbnQiID8gaHRtbFRvTWFya2Rvd24oY2xlYW4pIDogKGNsZWFuLnRleHRDb250ZW50IHx8ICIiKS50cmltKCk7CiAgfQogIGZ1bmN0aW9uIGJ1YmJsZVBsYWluKGJ1YmJsZSwgcm9sZSkgewogICAgcmV0dXJuIChzYW5pdGl6ZUNsb25lKGNvbnRlbnROb2RlT2YoYnViYmxlLCByb2xlKSkudGV4dENvbnRlbnQgfHwgIiIpLnRyaW0oKTsKICB9CgogIC8vIEJ1aWxkIGEgc2luZ2xlIGNvbnRyb2w6IGEgcHJpbWFyeSAiQ29weSIgKG1hcmtkb3duKSBwbHVzIGEgc21hbGwgY2FyZXQgdGhhdAogIC8vIHRvZ2dsZXMgYSBtZW51IHdpdGggIkNvcHkgYXMgcGxhaW4gdGV4dCIuIEFsbCBub2RlcyBjYXJyeSB0aGUgQ09OVFJPTF9QUkVGSVgKICAvLyBjbGFzcyBzbyBzYW5pdGl6ZUNsb25lIHJlbW92ZXMgdGhlbSBmcm9tIGFueSBjb3BpZWQgY29udGVudC4KICBmdW5jdGlvbiBidWlsZENvbnRyb2wob25NYXJrZG93biwgb25QbGFpbikgewogICAgdmFyIHdyYXAgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzcGFuIik7CiAgICB3cmFwLmNsYXNzTmFtZSA9IENPTlRST0xfUFJFRklYOwogICAgdmFyIHByaW1hcnkgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJidXR0b24iKTsKICAgIHByaW1hcnkudHlwZSA9ICJidXR0b24iOwogICAgcHJpbWFyeS5jbGFzc05hbWUgPSBDT05UUk9MX1BSRUZJWCArICItYnRuIjsKICAgIHByaW1hcnkudGl0bGUgPSAiQ29weSBhcyBNYXJrZG93biI7CiAgICBwcmltYXJ5LnRleHRDb250ZW50ID0gIkNvcHkiOwogICAgcHJpbWFyeS5hZGRFdmVudExpc3RlbmVyKCJjbGljayIsIGZ1bmN0aW9uIChlKSB7IGUuc3RvcFByb3BhZ2F0aW9uKCk7IG9uTWFya2Rvd24ocHJpbWFyeSk7IH0pOwogICAgdmFyIGNhcmV0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiYnV0dG9uIik7CiAgICBjYXJldC50eXBlID0gImJ1dHRvbiI7CiAgICBjYXJldC5jbGFzc05hbWUgPSBDT05UUk9MX1BSRUZJWCArICItY2FyZXQiOwogICAgY2FyZXQudGl0bGUgPSAiQ29weSBvcHRpb25zIjsKICAgIGNhcmV0LnRleHRDb250ZW50ID0gIuKWviI7IC8vIGJsYWNrIGRvd24tcG9pbnRpbmcgc21hbGwgdHJpYW5nbGUKICAgIHZhciBtZW51ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic3BhbiIpOwogICAgbWVudS5jbGFzc05hbWUgPSBDT05UUk9MX1BSRUZJWCArICItbWVudSI7CiAgICBtZW51LnN0eWxlLmRpc3BsYXkgPSAibm9uZSI7CiAgICB2YXIgcGxhaW4gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJidXR0b24iKTsKICAgIHBsYWluLnR5cGUgPSAiYnV0dG9uIjsKICAgIHBsYWluLmNsYXNzTmFtZSA9IENPTlRST0xfUFJFRklYICsgIi1idG4iOwogICAgcGxhaW4udGV4dENvbnRlbnQgPSAiQ29weSBhcyBwbGFpbiB0ZXh0IjsKICAgIHBsYWluLmFkZEV2ZW50TGlzdGVuZXIoImNsaWNrIiwgZnVuY3Rpb24gKGUpIHsgZS5zdG9wUHJvcGFnYXRpb24oKTsgbWVudS5zdHlsZS5kaXNwbGF5ID0gIm5vbmUiOyBvblBsYWluKHBsYWluKTsgfSk7CiAgICBtZW51LmFwcGVuZENoaWxkKHBsYWluKTsKICAgIGNhcmV0LmFkZEV2ZW50TGlzdGVuZXIoImNsaWNrIiwgZnVuY3Rpb24gKGUpIHsKICAgICAgZS5zdG9wUHJvcGFnYXRpb24oKTsKICAgICAgbWVudS5zdHlsZS5kaXNwbGF5ID0gbWVudS5zdHlsZS5kaXNwbGF5ID09PSAibm9uZSIgPyAiaW5saW5lLWJsb2NrIiA6ICJub25lIjsKICAgIH0pOwogICAgd3JhcC5hcHBlbmRDaGlsZChwcmltYXJ5KTsKICAgIHdyYXAuYXBwZW5kQ2hpbGQoY2FyZXQpOwogICAgd3JhcC5hcHBlbmRDaGlsZChtZW51KTsKICAgIHJldHVybiB3cmFwOwogIH0KCiAgZnVuY3Rpb24gZGVjb3JhdGUoYnViYmxlKSB7CiAgICB0cnkgewogICAgICB2YXIgcm9sZSA9IGNsYXNzaWZ5QnViYmxlKGJ1YmJsZSk7CiAgICAgIGlmICghcm9sZSkgcmV0dXJuOwogICAgICBpZiAocXMoYnViYmxlLCAiLiIgKyBDT05UUk9MX1BSRUZJWCkpIHJldHVybjsgLy8gYWxyZWFkeSBkZWNvcmF0ZWQKICAgICAgdmFyIGNvbnRyb2wgPSBidWlsZENvbnRyb2woCiAgICAgICAgZnVuY3Rpb24gKGhvc3QpIHsgY29weVRleHQoYnViYmxlTWFya2Rvd24oYnViYmxlLCByb2xlKSkudGhlbihmdW5jdGlvbiAoKSB7IGZsYXNoRmVlZGJhY2soY29udHJvbCk7IH0pOyB9LAogICAgICAgIGZ1bmN0aW9uIChob3N0KSB7IGNvcHlUZXh0KGJ1YmJsZVBsYWluKGJ1YmJsZSwgcm9sZSkpLnRoZW4oZnVuY3Rpb24gKCkgeyBmbGFzaEZlZWRiYWNrKGNvbnRyb2wpOyB9KTsgfQogICAgICApOwogICAgICBidWJibGUuYXBwZW5kQ2hpbGQoY29udHJvbCk7CiAgICB9IGNhdGNoIChfKSB7fQogIH0KCiAgZnVuY3Rpb24gY29weUNvbnZlcnNhdGlvbihmb3JtYXQpIHsKICAgIHZhciBidWJibGVzID0gcXNhKFVTRVJfQlVCQkxFICsgIiwiICsgQVNTSVNUQU5UX0JVQkJMRSk7CiAgICBpZiAoZm9ybWF0ID09PSAidGV4dCIpIHsKICAgICAgdmFyIGxpbmVzID0gW107CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgYnViYmxlcy5sZW5ndGg7IGkrKykgewogICAgICAgIHZhciByb2xlID0gY2xhc3NpZnlCdWJibGUoYnViYmxlc1tpXSk7CiAgICAgICAgaWYgKCFyb2xlKSBjb250aW51ZTsKICAgICAgICB2YXIgYm9keSA9IGJ1YmJsZVBsYWluKGJ1YmJsZXNbaV0sIHJvbGUpOwogICAgICAgIGlmIChib2R5KSBsaW5lcy5wdXNoKGJvZHkpOwogICAgICB9CiAgICAgIHJldHVybiBjb3B5VGV4dChsaW5lcy5qb2luKCJcblxuIikgKyAobGluZXMubGVuZ3RoID8gIlxuIiA6ICIiKSk7CiAgICB9CiAgICByZXR1cm4gY29weVRleHQoY29udmVyc2F0aW9uVG9NYXJrZG93bihidWJibGVzLCBmdW5jdGlvbiAoYikgewogICAgICByZXR1cm4gY29udGVudE5vZGVPZihiLCBjbGFzc2lmeUJ1YmJsZShiKSk7CiAgICB9KSk7CiAgfQoKICBmdW5jdGlvbiBpbnN0YWxsQ29udmVyc2F0aW9uQ29udHJvbCgpIHsKICAgIHRyeSB7CiAgICAgIGlmIChxcyhkb2N1bWVudCwgIi4iICsgQ09OVFJPTF9QUkVGSVggKyAiLWNvbnZlcnNhdGlvbiIpKSByZXR1cm47CiAgICAgIHZhciBiYXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKTsKICAgICAgYmFyLmNsYXNzTmFtZSA9IENPTlRST0xfUFJFRklYICsgIi1jb252ZXJzYXRpb24iOwogICAgICB2YXIgY29udHJvbCA9IGJ1aWxkQ29udHJvbCgKICAgICAgICBmdW5jdGlvbiAoKSB7IGNvcHlDb252ZXJzYXRpb24oIm1hcmtkb3duIikudGhlbihmdW5jdGlvbiAoKSB7IGZsYXNoRmVlZGJhY2soYmFyKTsgfSk7IH0sCiAgICAgICAgZnVuY3Rpb24gKCkgeyBjb3B5Q29udmVyc2F0aW9uKCJ0ZXh0IikudGhlbihmdW5jdGlvbiAoKSB7IGZsYXNoRmVlZGJhY2soYmFyKTsgfSk7IH0KICAgICAgKTsKICAgICAgY29udHJvbC50aXRsZSA9ICJDb3B5IGVudGlyZSBjb252ZXJzYXRpb24iOwogICAgICBiYXIuYXBwZW5kQ2hpbGQoY29udHJvbCk7CiAgICAgIGRvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQoYmFyKTsgLy8gZml4ZWQtcG9zaXRpb24gdmlhIENTUzsgcGxhY2VtZW50IHJlZmluZWQgaW4gVGFzayA2CiAgICB9IGNhdGNoIChfKSB7fQogIH0KCiAgZnVuY3Rpb24gc3dlZXAoKSB7IHZhciBiID0gcXNhKFVTRVJfQlVCQkxFICsgIiwiICsgQVNTSVNUQU5UX0JVQkJMRSk7IGZvciAodmFyIGkgPSAwOyBpIDwgYi5sZW5ndGg7IGkrKykgZGVjb3JhdGUoYltpXSk7IH0KCiAgZnVuY3Rpb24gYm9vdCgpIHsKICAgIHRyeSB7CiAgICAgIHZhciB0YXJnZXQgPSAoTUVTU0FHRVNfQ09OVEFJTkVSICYmIHFzKGRvY3VtZW50LCBNRVNTQUdFU19DT05UQUlORVIpKSB8fCBkb2N1bWVudC5ib2R5OwogICAgICBzd2VlcCgpOwogICAgICBpbnN0YWxsQ29udmVyc2F0aW9uQ29udHJvbCgpOwogICAgICBpZiAodHlwZW9mIE11dGF0aW9uT2JzZXJ2ZXIgPT09ICJ1bmRlZmluZWQiKSByZXR1cm47CiAgICAgIHZhciBvYnMgPSBuZXcgTXV0YXRpb25PYnNlcnZlcihmdW5jdGlvbiAoKSB7IHN3ZWVwKCk7IH0pOwogICAgICBvYnMub2JzZXJ2ZSh0YXJnZXQsIHsgY2hpbGRMaXN0OiB0cnVlLCBzdWJ0cmVlOiB0cnVlIH0pOwogICAgfSBjYXRjaCAoXykge30KICB9Cn0pKCk7Cg==").decode("utf-8") INJECT_CSS = base64.b64decode("LmNjLW1kLWNvcHkgewogIGRpc3BsYXk6IGlubGluZS1mbGV4OwogIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgZ2FwOiAycHg7CiAgdmVydGljYWwtYWxpZ246IG1pZGRsZTsKICBtYXJnaW4tbGVmdDogNnB4Owp9Ci5jYy1tZC1jb3B5LWJ0biwKLmNjLW1kLWNvcHktY2FyZXQgewogIGZvbnQ6IGluaGVyaXQ7CiAgZm9udC1zaXplOiAxMXB4OwogIGxpbmUtaGVpZ2h0OiAxLjQ7CiAgcGFkZGluZzogMXB4IDZweDsKICBjb2xvcjogdmFyKC0tdnNjb2RlLWZvcmVncm91bmQpOwogIGJhY2tncm91bmQ6IHRyYW5zcGFyZW50OwogIGJvcmRlcjogMXB4IHNvbGlkIHZhcigtLXZzY29kZS13aWRnZXQtYm9yZGVyLCB0cmFuc3BhcmVudCk7CiAgYm9yZGVyLXJhZGl1czogNHB4OwogIGN1cnNvcjogcG9pbnRlcjsKICBvcGFjaXR5OiAwLjY1Owp9Ci5jYy1tZC1jb3B5LWJ0bjpob3ZlciwKLmNjLW1kLWNvcHktY2FyZXQ6aG92ZXIgewogIG9wYWNpdHk6IDE7CiAgYmFja2dyb3VuZDogdmFyKC0tdnNjb2RlLXRvb2xiYXItaG92ZXJCYWNrZ3JvdW5kLCByZ2JhKDEyOCwgMTI4LCAxMjgsIDAuMTUpKTsKfQouY2MtbWQtY29weS1tZW51IHsKICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgbWFyZ2luLWxlZnQ6IDRweDsKICBwYWRkaW5nOiAycHg7CiAgYmFja2dyb3VuZDogdmFyKC0tdnNjb2RlLW1lbnUtYmFja2dyb3VuZCwgdmFyKC0tdnNjb2RlLWVkaXRvcldpZGdldC1iYWNrZ3JvdW5kKSk7CiAgYm9yZGVyOiAxcHggc29saWQgdmFyKC0tdnNjb2RlLW1lbnUtYm9yZGVyLCB2YXIoLS12c2NvZGUtd2lkZ2V0LWJvcmRlciwgdHJhbnNwYXJlbnQpKTsKICBib3JkZXItcmFkaXVzOiA0cHg7CiAgei1pbmRleDogNTsKfQouY2MtbWQtY29weS1mZWVkYmFjayB7CiAgbWFyZ2luLWxlZnQ6IDZweDsKICBmb250LXNpemU6IDExcHg7CiAgb3BhY2l0eTogMC44NTsKICBjb2xvcjogdmFyKC0tdnNjb2RlLWZvcmVncm91bmQpOwp9Ci5jYy1tZC1jb3B5LWNvbnZlcnNhdGlvbiB7CiAgcG9zaXRpb246IGZpeGVkOwogIHJpZ2h0OiAxNnB4OwogIGJvdHRvbTogNTZweDsKICB6LWluZGV4OiAxMDsKICBwYWRkaW5nOiAycHg7CiAgYmFja2dyb3VuZDogdmFyKC0tdnNjb2RlLWVkaXRvcldpZGdldC1iYWNrZ3JvdW5kKTsKICBib3JkZXI6IDFweCBzb2xpZCB2YXIoLS12c2NvZGUtd2lkZ2V0LWJvcmRlciwgdHJhbnNwYXJlbnQpOwogIGJvcmRlci1yYWRpdXM6IDZweDsKICBvcGFjaXR5OiAwLjg1Owp9Ci5jYy1tZC1jb3B5LWNvbnZlcnNhdGlvbjpob3ZlciB7CiAgb3BhY2l0eTogMTsKfQo=").decode("utf-8") # <<>>CCWA-MD-COPY-EMBED>>> (generated by tools/gen-embeds; do not edit) -const MD_COPY_JS = "/* cc-md-copy: per-message and whole-conversation copy (markdown/plain) for the\n * Claude Code VS Code webview. Self-contained IIFE appended to webview/index.js.\n * Additive and read-only w.r.t. app state; keyed on stable CSS-module class\n * prefixes, so it fails safe (controls simply do not appear) if a prefix moves.\n * Exposes its pure functions for node unit tests; boot()s only in a real webview. */\n(function () {\n \"use strict\";\n\n var CONTROL_PREFIX = \"cc-md-copy\"; // every injected node's class starts with this\n var USER_BUBBLE = '[class*=\"userMessageContainer_\"]';\n // Assistant message wrapper. Verified on 2.1.170: the render emits exactly one\n // `data-testid=\"assistant-message\"` div per assistant turn, with the rating\n // widget and content blocks as its children. (The earlier `[data-message-rating]`\n // was WRONG: that attribute sits on the nested rating control, which is also only\n // rendered behind an experiment+analytics gate.) Re-pinned in Task 6.\n var ASSISTANT_BUBBLE = '[data-testid=\"assistant-message\"]';\n var MESSAGES_CONTAINER = '[class*=\"messagesContainer_\"]'; // e.g. '[class*=\"timeline_\"]'; \"\" -> observe document.body\n // Optional narrowing only. MUST be a single wrapper around ALL content blocks,\n // not a per-block class (a turn has multiple blocks). \"\" -> use the bubble itself\n // (already aggregates all blocks; sanitizeClone is the correctness gate).\n var ASSISTANT_CONTENT = \"\";\n var FEEDBACK_MS = 1800;\n\n // ---- HTML -> Markdown (DOM walk) -------------------------------------------\n // Uses only: nodeType, tagName, childNodes, textContent, getAttribute, className.\n function htmlToMarkdown(root) {\n // Longest run of consecutive backticks in s, so a code delimiter/fence can be\n // chosen longer than anything inside it (else ``` in the content closes early).\n function backtickRun(s) {\n var max = 0, cur = 0;\n for (var i = 0; i < s.length; i++) {\n if (s.charAt(i) === \"`\") { cur++; if (cur > max) max = cur; } else cur = 0;\n }\n return max;\n }\n function fence(s, min) { var n = backtickRun(s) + 1; if (n < min) n = min; return new Array(n + 1).join(\"`\"); }\n function inline(node) {\n var out = \"\";\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 3) { out += c.textContent || \"\"; continue; }\n if (c.nodeType !== 1) continue;\n var tag = (c.tagName || \"\").toUpperCase();\n if (tag === \"BR\") out += \"\\n\";\n else if (tag === \"STRONG\" || tag === \"B\") out += \"**\" + inline(c) + \"**\";\n else if (tag === \"EM\" || tag === \"I\") out += \"*\" + inline(c) + \"*\";\n else if (tag === \"DEL\" || tag === \"S\") out += \"~~\" + inline(c) + \"~~\";\n else if (tag === \"CODE\") {\n var ct = c.textContent || \"\";\n var d = fence(ct, 1);\n // CommonMark strips one leading+trailing space, so pad when an edge is a\n // backtick to keep it from merging with the delimiter.\n var p = (ct.charAt(0) === \"`\" || ct.charAt(ct.length - 1) === \"`\") ? \" \" : \"\";\n out += d + p + ct + p + d;\n }\n else if (tag === \"A\") {\n var href = c.getAttribute ? c.getAttribute(\"href\") : null;\n var t = inline(c);\n out += href ? \"[\" + t + \"](\" + href + \")\" : t;\n } else out += inline(c); // unknown inline wrapper: keep text, drop tag\n }\n return out;\n }\n function langOf(codeEl) {\n var cls = \"\";\n if (codeEl) cls = (codeEl.getAttribute && codeEl.getAttribute(\"class\")) || codeEl.className || \"\";\n var m = /language-([A-Za-z0-9+#.\\-]+)/.exec(cls || \"\");\n return m ? m[1] : \"\";\n }\n function findChildTag(node, tag) {\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n if (kids[i].nodeType === 1 && (kids[i].tagName || \"\").toUpperCase() === tag) return kids[i];\n }\n return null;\n }\n function list(node, ordered, depth) {\n var out = \"\", n = 1;\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var li = kids[i];\n if (li.nodeType !== 1 || (li.tagName || \"\").toUpperCase() !== \"LI\") continue;\n var marker = ordered ? n++ + \". \" : \"- \";\n var indent = new Array(depth + 1).join(\" \");\n var lead = \"\", nested = \"\";\n var lk = li.childNodes || [];\n for (var j = 0; j < lk.length; j++) {\n var ch = lk[j];\n var ct = ch.nodeType === 1 ? (ch.tagName || \"\").toUpperCase() : \"\";\n if (ct === \"UL\") nested += list(ch, false, depth + 1);\n else if (ct === \"OL\") nested += list(ch, true, depth + 1);\n else if (ch.nodeType === 3) lead += ch.textContent || \"\";\n else lead += inline(ch);\n }\n out += indent + marker + lead.trim() + \"\\n\" + nested;\n }\n return out;\n }\n function table(node) {\n var rows = [];\n (function collect(container) {\n var kids = container.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType !== 1) continue;\n var t = (c.tagName || \"\").toUpperCase();\n if (t === \"THEAD\" || t === \"TBODY\" || t === \"TFOOT\") collect(c);\n else if (t === \"TR\") {\n var cells = [], cc = c.childNodes || [];\n for (var j = 0; j < cc.length; j++) {\n var d = cc[j];\n if (d.nodeType !== 1) continue;\n var dt = (d.tagName || \"\").toUpperCase();\n if (dt === \"TH\" || dt === \"TD\") cells.push(inline(d).trim());\n }\n rows.push(cells);\n }\n }\n })(node);\n if (!rows.length) return \"\";\n var head = rows[0], body = rows.slice(1);\n var sep = head.map(function () { return \"---\"; });\n var out = \"| \" + head.join(\" | \") + \" |\\n| \" + sep.join(\" | \") + \" |\\n\";\n for (var k = 0; k < body.length; k++) out += \"| \" + body[k].join(\" | \") + \" |\\n\";\n return out;\n }\n function block(node) {\n var out = \"\";\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 3) { if ((c.textContent || \"\").trim()) out += c.textContent; continue; }\n if (c.nodeType !== 1) continue;\n var tag = (c.tagName || \"\").toUpperCase();\n if (/^H[1-6]$/.test(tag)) out += new Array(+tag[1] + 1).join(\"#\") + \" \" + inline(c).trim() + \"\\n\\n\";\n else if (tag === \"P\") out += inline(c).trim() + \"\\n\\n\";\n else if (tag === \"UL\") out += list(c, false, 0) + \"\\n\";\n else if (tag === \"OL\") out += list(c, true, 0) + \"\\n\";\n else if (tag === \"PRE\") {\n var code = findChildTag(c, \"CODE\");\n var lang = langOf(code || c);\n var body = (code || c).textContent || \"\";\n var f = fence(body, 3);\n out += f + lang + \"\\n\" + body.replace(/\\n$/, \"\") + \"\\n\" + f + \"\\n\\n\";\n } else if (tag === \"BLOCKQUOTE\") {\n var inner = block(c).trim().split(\"\\n\").map(function (l) { return \"> \" + l; }).join(\"\\n\");\n out += inner + \"\\n\\n\";\n } else if (tag === \"HR\") out += \"---\\n\\n\";\n else if (tag === \"TABLE\") out += table(c) + \"\\n\";\n else if (tag === \"BR\") out += \"\\n\";\n else if (tag === \"STRONG\" || tag === \"B\" || tag === \"EM\" || tag === \"I\" ||\n tag === \"A\" || tag === \"CODE\" || tag === \"DEL\" || tag === \"S\")\n out += inline(c) + \"\\n\\n\";\n else out += block(c); // unknown wrapper: recurse (drop tag, keep content)\n }\n return out;\n }\n // block() dispatches on each CHILD's tag, treating the passed node as a plain\n // container. Wrap root in a one-off container so root's OWN tag is dispatched\n // too: callers pass either the bubble container (its block children render) or\n // a single block element like
/
    / (now handled, not flattened).\n return block({ childNodes: [root] }).replace(/\\n{3,}/g, \"\\n\\n\").trim();\n }\n\n // ---- pure helpers ----------------------------------------------------------\n function hasPrefix(node, prefix) {\n if (node.nodeType !== 1 || typeof node.className !== \"string\") return false;\n var parts = node.className.split(/\\s+/);\n for (var i = 0; i < parts.length; i++) if (parts[i].indexOf(prefix) === 0) return true;\n return false;\n }\n\n // Class-prefix hooks for non-content chrome that renders *inside* an assistant\n // bubble (verified on 2.1.170; Task 6 re-pins these). tool*/thinking_ are the v1\n // exclusions; unknownContent_ is the renderer's fallback for unrecognized block\n // types, so stripping it makes a *future* block type fail safe to excluded rather\n // than leaking \"Unsupported content\" into the copy. Re-pin if a prefix moves.\n var CHROME_PREFIXES = [\"toolUse_\", \"toolResult_\", \"toolReference_\", \"thinking_\", \"unknownContent_\"];\n\n // True for any node that must never appear in copied output: our own controls,\n // the rating widget (`data-message-rating` + its \"Thanks for your feedback\"\n // text), any button (copy-code chrome), and the excluded content blocks above.\n function isChrome(node) {\n if (node.nodeType !== 1) return false;\n if ((node.tagName || \"\").toUpperCase() === \"BUTTON\") return true;\n if (node.getAttribute && node.getAttribute(\"data-message-rating\") !== null) return true;\n if (hasPrefix(node, CONTROL_PREFIX)) return true;\n for (var i = 0; i < CHROME_PREFIXES.length; i++) if (hasPrefix(node, CHROME_PREFIXES[i])) return true;\n return false;\n }\n\n // Deep-clone `contentNode`, then strip every chrome node so copied output is the\n // message's text content only. This is a CORRECTNESS GATE, not cosmetic: the\n // default content node is the whole bubble (all content-block siblings, so multi-\n // block assistant turns are captured), and this strip-list is the only thing\n // keeping the rating widget and v1-excluded blocks out of the copy.\n function sanitizeClone(contentNode) {\n var clone = contentNode.cloneNode(true);\n (function strip(node) {\n var kids = (node.childNodes || []).slice();\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 1 && isChrome(c)) { node.removeChild(c); continue; }\n if (c.nodeType === 1) strip(c);\n }\n })(clone);\n return clone;\n }\n\n function classifyBubble(node) {\n if (node.nodeType !== 1) return null;\n if (hasPrefix(node, \"userMessageContainer_\")) return \"user\";\n if (node.getAttribute && node.getAttribute(\"data-testid\") === \"assistant-message\") return \"assistant\";\n return null;\n }\n\n // Build the whole-conversation markdown from an ordered list of bubbles.\n // `contentOf(bubble)` resolves the content node (default: the bubble itself, so\n // every content block is included; sanitizeClone drops chrome); a default is\n // provided for tests.\n function conversationToMarkdown(bubbles, contentOf) {\n contentOf = contentOf || function (b) { return b; };\n var parts = [];\n for (var i = 0; i < bubbles.length; i++) {\n var role = classifyBubble(bubbles[i]);\n if (!role) continue;\n var clean = sanitizeClone(contentOf(bubbles[i]));\n var body = role === \"assistant\" ? htmlToMarkdown(clean) : (clean.textContent || \"\").trim();\n if (!body) continue;\n parts.push((role === \"user\" ? \"## User\" : \"## Assistant\") + \"\\n\\n\" + body);\n }\n return parts.join(\"\\n\\n\") + (parts.length ? \"\\n\" : \"\");\n }\n\n // ---- exports (node tests) / boot (real webview) ----------------------------\n if (typeof document !== \"undefined\") {\n boot();\n } else if (typeof module !== \"undefined\" && module.exports) {\n module.exports = { htmlToMarkdown: htmlToMarkdown, sanitizeClone: sanitizeClone,\n classifyBubble: classifyBubble, conversationToMarkdown: conversationToMarkdown };\n }\n\n // ---- live-webview wiring (runs only when a document exists) ----------------\n function qs(node, sel) { try { return sel && node.querySelector ? node.querySelector(sel) : null; } catch (_) { return null; } }\n function qsa(sel) { try { return Array.prototype.slice.call(document.querySelectorAll(sel)); } catch (_) { return []; } }\n\n // The content node to convert/copy: the optional ASSISTANT_CONTENT wrapper if\n // pinned and present, else the bubble itself. The bubble already contains every\n // content-block sibling of a multi-block turn, and sanitizeClone strips the\n // chrome (rating widget, tool/thinking/unknown blocks, buttons, our controls)\n // either way -- so this is a narrowing, never the thing that guarantees\n // correctness.\n function contentNodeOf(bubble, role) {\n if (role === \"assistant\" && ASSISTANT_CONTENT) {\n var n = qs(bubble, ASSISTANT_CONTENT);\n if (n) return n;\n }\n return bubble;\n }\n\n function copyText(text) {\n try {\n if (navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(text);\n } catch (_) {}\n return Promise.resolve(); // best-effort; never throw into the app\n }\n\n function flashFeedback(host) {\n try {\n var fb = document.createElement(\"span\");\n fb.className = CONTROL_PREFIX + \"-feedback\";\n fb.textContent = \"Copied\";\n host.appendChild(fb);\n setTimeout(function () { if (fb && fb.parentNode) fb.parentNode.removeChild(fb); }, FEEDBACK_MS);\n } catch (_) {}\n }\n\n function bubbleMarkdown(bubble, role) {\n var clean = sanitizeClone(contentNodeOf(bubble, role));\n return role === \"assistant\" ? htmlToMarkdown(clean) : (clean.textContent || \"\").trim();\n }\n function bubblePlain(bubble, role) {\n return (sanitizeClone(contentNodeOf(bubble, role)).textContent || \"\").trim();\n }\n\n // Build a single control: a primary \"Copy\" (markdown) plus a small caret that\n // toggles a menu with \"Copy as plain text\". All nodes carry the CONTROL_PREFIX\n // class so sanitizeClone removes them from any copied content.\n function buildControl(onMarkdown, onPlain) {\n var wrap = document.createElement(\"span\");\n wrap.className = CONTROL_PREFIX;\n var primary = document.createElement(\"button\");\n primary.type = \"button\";\n primary.className = CONTROL_PREFIX + \"-btn\";\n primary.title = \"Copy as Markdown\";\n primary.textContent = \"Copy\";\n primary.addEventListener(\"click\", function (e) { e.stopPropagation(); onMarkdown(primary); });\n var caret = document.createElement(\"button\");\n caret.type = \"button\";\n caret.className = CONTROL_PREFIX + \"-caret\";\n caret.title = \"Copy options\";\n caret.textContent = \"\u25be\"; // black down-pointing small triangle\n var menu = document.createElement(\"span\");\n menu.className = CONTROL_PREFIX + \"-menu\";\n menu.style.display = \"none\";\n var plain = document.createElement(\"button\");\n plain.type = \"button\";\n plain.className = CONTROL_PREFIX + \"-btn\";\n plain.textContent = \"Copy as plain text\";\n plain.addEventListener(\"click\", function (e) { e.stopPropagation(); menu.style.display = \"none\"; onPlain(plain); });\n menu.appendChild(plain);\n caret.addEventListener(\"click\", function (e) {\n e.stopPropagation();\n menu.style.display = menu.style.display === \"none\" ? \"inline-block\" : \"none\";\n });\n wrap.appendChild(primary);\n wrap.appendChild(caret);\n wrap.appendChild(menu);\n return wrap;\n }\n\n function decorate(bubble) {\n try {\n var role = classifyBubble(bubble);\n if (!role) return;\n if (qs(bubble, \".\" + CONTROL_PREFIX)) return; // already decorated\n var control = buildControl(\n function (host) { copyText(bubbleMarkdown(bubble, role)).then(function () { flashFeedback(control); }); },\n function (host) { copyText(bubblePlain(bubble, role)).then(function () { flashFeedback(control); }); }\n );\n bubble.appendChild(control);\n } catch (_) {}\n }\n\n function copyConversation(format) {\n var bubbles = qsa(USER_BUBBLE + \",\" + ASSISTANT_BUBBLE);\n if (format === \"text\") {\n var lines = [];\n for (var i = 0; i < bubbles.length; i++) {\n var role = classifyBubble(bubbles[i]);\n if (!role) continue;\n var body = bubblePlain(bubbles[i], role);\n if (body) lines.push(body);\n }\n return copyText(lines.join(\"\\n\\n\") + (lines.length ? \"\\n\" : \"\"));\n }\n return copyText(conversationToMarkdown(bubbles, function (b) {\n return contentNodeOf(b, classifyBubble(b));\n }));\n }\n\n function installConversationControl() {\n try {\n if (qs(document, \".\" + CONTROL_PREFIX + \"-conversation\")) return;\n var bar = document.createElement(\"div\");\n bar.className = CONTROL_PREFIX + \"-conversation\";\n var control = buildControl(\n function () { copyConversation(\"markdown\").then(function () { flashFeedback(bar); }); },\n function () { copyConversation(\"text\").then(function () { flashFeedback(bar); }); }\n );\n control.title = \"Copy entire conversation\";\n bar.appendChild(control);\n document.body.appendChild(bar); // fixed-position via CSS; placement refined in Task 6\n } catch (_) {}\n }\n\n function sweep() { var b = qsa(USER_BUBBLE + \",\" + ASSISTANT_BUBBLE); for (var i = 0; i < b.length; i++) decorate(b[i]); }\n\n function boot() {\n try {\n var target = (MESSAGES_CONTAINER && qs(document, MESSAGES_CONTAINER)) || document.body;\n sweep();\n installConversationControl();\n if (typeof MutationObserver === \"undefined\") return;\n var obs = new MutationObserver(function () { sweep(); });\n obs.observe(target, { childList: true, subtree: true });\n } catch (_) {}\n }\n})();\n"; +const MD_COPY_JS = "/* cc-md-copy: per-message and whole-conversation copy (markdown/plain) for the\n * Claude Code VS Code webview. Self-contained IIFE appended to webview/index.js.\n * Additive and read-only w.r.t. app state; keyed on stable CSS-module class\n * prefixes, so it fails safe (controls simply do not appear) if a prefix moves.\n * Exposes its pure functions for node unit tests; boot()s only in a real webview. */\n/* Leading ';' so that, appended after the bundle, this IIFE can never be parsed as\n * a call on the bundle's final expression if it lacks a trailing semicolon (ASI\n * safety across extension builds). */\n;(function () {\n \"use strict\";\n\n var CONTROL_PREFIX = \"cc-md-copy\"; // every injected node's class starts with this\n var USER_BUBBLE = '[class*=\"userMessageContainer_\"]';\n // Assistant message wrapper. Verified on 2.1.170: the render emits exactly one\n // `data-testid=\"assistant-message\"` div per assistant turn, with the rating\n // widget and content blocks as its children. (The earlier `[data-message-rating]`\n // was WRONG: that attribute sits on the nested rating control, which is also only\n // rendered behind an experiment+analytics gate.) Re-pinned in Task 6.\n var ASSISTANT_BUBBLE = '[data-testid=\"assistant-message\"]';\n var MESSAGES_CONTAINER = '[class*=\"messagesContainer_\"]'; // e.g. '[class*=\"timeline_\"]'; \"\" -> observe document.body\n // Optional narrowing only. MUST be a single wrapper around ALL content blocks,\n // not a per-block class (a turn has multiple blocks). \"\" -> use the bubble itself\n // (already aggregates all blocks; sanitizeClone is the correctness gate).\n var ASSISTANT_CONTENT = \"\";\n var FEEDBACK_MS = 1800;\n\n // ---- HTML -> Markdown (DOM walk) -------------------------------------------\n // Uses only: nodeType, tagName, childNodes, textContent, getAttribute, className.\n function htmlToMarkdown(root) {\n // Longest run of consecutive backticks in s, so a code delimiter/fence can be\n // chosen longer than anything inside it (else ``` in the content closes early).\n function backtickRun(s) {\n var max = 0, cur = 0;\n for (var i = 0; i < s.length; i++) {\n if (s.charAt(i) === \"`\") { cur++; if (cur > max) max = cur; } else cur = 0;\n }\n return max;\n }\n function fence(s, min) { var n = backtickRun(s) + 1; if (n < min) n = min; return new Array(n + 1).join(\"`\"); }\n function inline(node) {\n var out = \"\";\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 3) { out += c.textContent || \"\"; continue; }\n if (c.nodeType !== 1) continue;\n var tag = (c.tagName || \"\").toUpperCase();\n if (tag === \"BR\") out += \"\\n\";\n else if (tag === \"STRONG\" || tag === \"B\") out += \"**\" + inline(c) + \"**\";\n else if (tag === \"EM\" || tag === \"I\") out += \"*\" + inline(c) + \"*\";\n else if (tag === \"DEL\" || tag === \"S\") out += \"~~\" + inline(c) + \"~~\";\n else if (tag === \"CODE\") {\n var ct = c.textContent || \"\";\n var d = fence(ct, 1);\n // CommonMark strips one leading+trailing space, so pad when an edge is a\n // backtick to keep it from merging with the delimiter.\n var p = (ct.charAt(0) === \"`\" || ct.charAt(ct.length - 1) === \"`\") ? \" \" : \"\";\n out += d + p + ct + p + d;\n }\n else if (tag === \"A\") {\n var href = c.getAttribute ? c.getAttribute(\"href\") : null;\n var t = inline(c);\n out += href ? \"[\" + t + \"](\" + href + \")\" : t;\n } else out += inline(c); // unknown inline wrapper: keep text, drop tag\n }\n return out;\n }\n function langOf(codeEl) {\n var cls = \"\";\n if (codeEl) cls = (codeEl.getAttribute && codeEl.getAttribute(\"class\")) || codeEl.className || \"\";\n var m = /language-([A-Za-z0-9+#.\\-]+)/.exec(cls || \"\");\n return m ? m[1] : \"\";\n }\n function findChildTag(node, tag) {\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n if (kids[i].nodeType === 1 && (kids[i].tagName || \"\").toUpperCase() === tag) return kids[i];\n }\n return null;\n }\n function list(node, ordered, depth) {\n var out = \"\", n = 1;\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var li = kids[i];\n if (li.nodeType !== 1 || (li.tagName || \"\").toUpperCase() !== \"LI\") continue;\n var marker = ordered ? n++ + \". \" : \"- \";\n var indent = new Array(depth + 1).join(\" \");\n var lead = \"\", nested = \"\";\n var lk = li.childNodes || [];\n for (var j = 0; j < lk.length; j++) {\n var ch = lk[j];\n var ct = ch.nodeType === 1 ? (ch.tagName || \"\").toUpperCase() : \"\";\n if (ct === \"UL\") nested += list(ch, false, depth + 1);\n else if (ct === \"OL\") nested += list(ch, true, depth + 1);\n else if (ch.nodeType === 3) lead += ch.textContent || \"\";\n else lead += inline(ch);\n }\n out += indent + marker + lead.trim() + \"\\n\" + nested;\n }\n return out;\n }\n function table(node) {\n var rows = [];\n (function collect(container) {\n var kids = container.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType !== 1) continue;\n var t = (c.tagName || \"\").toUpperCase();\n if (t === \"THEAD\" || t === \"TBODY\" || t === \"TFOOT\") collect(c);\n else if (t === \"TR\") {\n var cells = [], cc = c.childNodes || [];\n for (var j = 0; j < cc.length; j++) {\n var d = cc[j];\n if (d.nodeType !== 1) continue;\n var dt = (d.tagName || \"\").toUpperCase();\n if (dt === \"TH\" || dt === \"TD\") cells.push(inline(d).trim());\n }\n rows.push(cells);\n }\n }\n })(node);\n if (!rows.length) return \"\";\n var head = rows[0], body = rows.slice(1);\n var sep = head.map(function () { return \"---\"; });\n var out = \"| \" + head.join(\" | \") + \" |\\n| \" + sep.join(\" | \") + \" |\\n\";\n for (var k = 0; k < body.length; k++) out += \"| \" + body[k].join(\" | \") + \" |\\n\";\n return out;\n }\n function block(node) {\n var out = \"\";\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 3) { if ((c.textContent || \"\").trim()) out += c.textContent; continue; }\n if (c.nodeType !== 1) continue;\n var tag = (c.tagName || \"\").toUpperCase();\n if (/^H[1-6]$/.test(tag)) out += new Array(+tag[1] + 1).join(\"#\") + \" \" + inline(c).trim() + \"\\n\\n\";\n else if (tag === \"P\") out += inline(c).trim() + \"\\n\\n\";\n else if (tag === \"UL\") out += list(c, false, 0) + \"\\n\";\n else if (tag === \"OL\") out += list(c, true, 0) + \"\\n\";\n else if (tag === \"PRE\") {\n var code = findChildTag(c, \"CODE\");\n var lang = langOf(code || c);\n var body = (code || c).textContent || \"\";\n var f = fence(body, 3);\n out += f + lang + \"\\n\" + body.replace(/\\n$/, \"\") + \"\\n\" + f + \"\\n\\n\";\n } else if (tag === \"BLOCKQUOTE\") {\n var inner = block(c).trim().split(\"\\n\").map(function (l) { return \"> \" + l; }).join(\"\\n\");\n out += inner + \"\\n\\n\";\n } else if (tag === \"HR\") out += \"---\\n\\n\";\n else if (tag === \"TABLE\") out += table(c) + \"\\n\";\n else if (tag === \"BR\") out += \"\\n\";\n else if (tag === \"STRONG\" || tag === \"B\" || tag === \"EM\" || tag === \"I\" ||\n tag === \"A\" || tag === \"CODE\" || tag === \"DEL\" || tag === \"S\")\n out += inline(c) + \"\\n\\n\";\n else out += block(c); // unknown wrapper: recurse (drop tag, keep content)\n }\n return out;\n }\n // block() dispatches on each CHILD's tag, treating the passed node as a plain\n // container. Wrap root in a one-off container so root's OWN tag is dispatched\n // too: callers pass either the bubble container (its block children render) or\n // a single block element like
    /
      /
    (now handled, not flattened).\n return block({ childNodes: [root] }).replace(/\\n{3,}/g, \"\\n\\n\").trim();\n }\n\n // ---- pure helpers ----------------------------------------------------------\n function hasPrefix(node, prefix) {\n if (node.nodeType !== 1 || typeof node.className !== \"string\") return false;\n var parts = node.className.split(/\\s+/);\n for (var i = 0; i < parts.length; i++) if (parts[i].indexOf(prefix) === 0) return true;\n return false;\n }\n\n // Class-prefix hooks for non-content chrome that renders *inside* an assistant\n // bubble (verified on 2.1.170; Task 6 re-pins these). tool*/thinking_ are the v1\n // exclusions; unknownContent_ is the renderer's fallback for unrecognized block\n // types, so stripping it makes a *future* block type fail safe to excluded rather\n // than leaking \"Unsupported content\" into the copy. Re-pin if a prefix moves.\n var CHROME_PREFIXES = [\"toolUse_\", \"toolResult_\", \"toolReference_\", \"thinking_\", \"unknownContent_\"];\n\n // True for any node that must never appear in copied output: our own controls,\n // the rating widget (`data-message-rating` + its \"Thanks for your feedback\"\n // text), any button (copy-code chrome), and the excluded content blocks above.\n function isChrome(node) {\n if (node.nodeType !== 1) return false;\n if ((node.tagName || \"\").toUpperCase() === \"BUTTON\") return true;\n if (node.getAttribute && node.getAttribute(\"data-message-rating\") !== null) return true;\n if (hasPrefix(node, CONTROL_PREFIX)) return true;\n for (var i = 0; i < CHROME_PREFIXES.length; i++) if (hasPrefix(node, CHROME_PREFIXES[i])) return true;\n return false;\n }\n\n // Deep-clone `contentNode`, then strip every chrome node so copied output is the\n // message's text content only. This is a CORRECTNESS GATE, not cosmetic: the\n // default content node is the whole bubble (all content-block siblings, so multi-\n // block assistant turns are captured), and this strip-list is the only thing\n // keeping the rating widget and v1-excluded blocks out of the copy.\n function sanitizeClone(contentNode) {\n var clone = contentNode.cloneNode(true);\n (function strip(node) {\n var kids = (node.childNodes || []).slice();\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 1 && isChrome(c)) { node.removeChild(c); continue; }\n if (c.nodeType === 1) strip(c);\n }\n })(clone);\n return clone;\n }\n\n function classifyBubble(node) {\n if (node.nodeType !== 1) return null;\n if (hasPrefix(node, \"userMessageContainer_\")) return \"user\";\n if (node.getAttribute && node.getAttribute(\"data-testid\") === \"assistant-message\") return \"assistant\";\n return null;\n }\n\n // Build the whole-conversation markdown from an ordered list of bubbles.\n // `contentOf(bubble)` resolves the content node (default: the bubble itself, so\n // every content block is included; sanitizeClone drops chrome); a default is\n // provided for tests.\n function conversationToMarkdown(bubbles, contentOf) {\n contentOf = contentOf || function (b) { return b; };\n var parts = [];\n for (var i = 0; i < bubbles.length; i++) {\n var role = classifyBubble(bubbles[i]);\n if (!role) continue;\n var clean = sanitizeClone(contentOf(bubbles[i]));\n var body = role === \"assistant\" ? htmlToMarkdown(clean) : (clean.textContent || \"\").trim();\n if (!body) continue;\n parts.push((role === \"user\" ? \"## User\" : \"## Assistant\") + \"\\n\\n\" + body);\n }\n return parts.join(\"\\n\\n\") + (parts.length ? \"\\n\" : \"\");\n }\n\n // ---- exports (node tests) / boot (real webview) ----------------------------\n if (typeof document !== \"undefined\") {\n boot();\n } else if (typeof module !== \"undefined\" && module.exports) {\n module.exports = { htmlToMarkdown: htmlToMarkdown, sanitizeClone: sanitizeClone,\n classifyBubble: classifyBubble, conversationToMarkdown: conversationToMarkdown };\n }\n\n // ---- live-webview wiring (runs only when a document exists) ----------------\n function qs(node, sel) { try { return sel && node.querySelector ? node.querySelector(sel) : null; } catch (_) { return null; } }\n function qsa(sel) { try { return Array.prototype.slice.call(document.querySelectorAll(sel)); } catch (_) { return []; } }\n\n // The content node to convert/copy: the optional ASSISTANT_CONTENT wrapper if\n // pinned and present, else the bubble itself. The bubble already contains every\n // content-block sibling of a multi-block turn, and sanitizeClone strips the\n // chrome (rating widget, tool/thinking/unknown blocks, buttons, our controls)\n // either way -- so this is a narrowing, never the thing that guarantees\n // correctness.\n function contentNodeOf(bubble, role) {\n if (role === \"assistant\" && ASSISTANT_CONTENT) {\n var n = qs(bubble, ASSISTANT_CONTENT);\n if (n) return n;\n }\n return bubble;\n }\n\n function copyText(text) {\n try {\n if (navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(text);\n } catch (_) {}\n return Promise.resolve(); // best-effort; never throw into the app\n }\n\n function flashFeedback(host) {\n try {\n var fb = document.createElement(\"span\");\n fb.className = CONTROL_PREFIX + \"-feedback\";\n fb.textContent = \"Copied\";\n host.appendChild(fb);\n setTimeout(function () { if (fb && fb.parentNode) fb.parentNode.removeChild(fb); }, FEEDBACK_MS);\n } catch (_) {}\n }\n\n function bubbleMarkdown(bubble, role) {\n var clean = sanitizeClone(contentNodeOf(bubble, role));\n return role === \"assistant\" ? htmlToMarkdown(clean) : (clean.textContent || \"\").trim();\n }\n function bubblePlain(bubble, role) {\n return (sanitizeClone(contentNodeOf(bubble, role)).textContent || \"\").trim();\n }\n\n // Build a single control: a primary \"Copy\" (markdown) plus a small caret that\n // toggles a menu with \"Copy as plain text\". All nodes carry the CONTROL_PREFIX\n // class so sanitizeClone removes them from any copied content.\n function buildControl(onMarkdown, onPlain) {\n var wrap = document.createElement(\"span\");\n wrap.className = CONTROL_PREFIX;\n var primary = document.createElement(\"button\");\n primary.type = \"button\";\n primary.className = CONTROL_PREFIX + \"-btn\";\n primary.title = \"Copy as Markdown\";\n primary.textContent = \"Copy\";\n primary.addEventListener(\"click\", function (e) { e.stopPropagation(); onMarkdown(primary); });\n var caret = document.createElement(\"button\");\n caret.type = \"button\";\n caret.className = CONTROL_PREFIX + \"-caret\";\n caret.title = \"Copy options\";\n caret.textContent = \"\u25be\"; // black down-pointing small triangle\n var menu = document.createElement(\"span\");\n menu.className = CONTROL_PREFIX + \"-menu\";\n menu.style.display = \"none\";\n var plain = document.createElement(\"button\");\n plain.type = \"button\";\n plain.className = CONTROL_PREFIX + \"-btn\";\n plain.textContent = \"Copy as plain text\";\n plain.addEventListener(\"click\", function (e) { e.stopPropagation(); menu.style.display = \"none\"; onPlain(plain); });\n menu.appendChild(plain);\n caret.addEventListener(\"click\", function (e) {\n e.stopPropagation();\n menu.style.display = menu.style.display === \"none\" ? \"inline-block\" : \"none\";\n });\n wrap.appendChild(primary);\n wrap.appendChild(caret);\n wrap.appendChild(menu);\n return wrap;\n }\n\n function decorate(bubble) {\n try {\n var role = classifyBubble(bubble);\n if (!role) return;\n if (qs(bubble, \".\" + CONTROL_PREFIX)) return; // already decorated\n var control = buildControl(\n function (host) { copyText(bubbleMarkdown(bubble, role)).then(function () { flashFeedback(control); }); },\n function (host) { copyText(bubblePlain(bubble, role)).then(function () { flashFeedback(control); }); }\n );\n bubble.appendChild(control);\n } catch (_) {}\n }\n\n function copyConversation(format) {\n var bubbles = qsa(USER_BUBBLE + \",\" + ASSISTANT_BUBBLE);\n if (format === \"text\") {\n var lines = [];\n for (var i = 0; i < bubbles.length; i++) {\n var role = classifyBubble(bubbles[i]);\n if (!role) continue;\n var body = bubblePlain(bubbles[i], role);\n if (body) lines.push(body);\n }\n return copyText(lines.join(\"\\n\\n\") + (lines.length ? \"\\n\" : \"\"));\n }\n return copyText(conversationToMarkdown(bubbles, function (b) {\n return contentNodeOf(b, classifyBubble(b));\n }));\n }\n\n function installConversationControl() {\n try {\n if (qs(document, \".\" + CONTROL_PREFIX + \"-conversation\")) return;\n var bar = document.createElement(\"div\");\n bar.className = CONTROL_PREFIX + \"-conversation\";\n var control = buildControl(\n function () { copyConversation(\"markdown\").then(function () { flashFeedback(bar); }); },\n function () { copyConversation(\"text\").then(function () { flashFeedback(bar); }); }\n );\n control.title = \"Copy entire conversation\";\n bar.appendChild(control);\n document.body.appendChild(bar); // fixed-position via CSS; placement refined in Task 6\n } catch (_) {}\n }\n\n function sweep() { var b = qsa(USER_BUBBLE + \",\" + ASSISTANT_BUBBLE); for (var i = 0; i < b.length; i++) decorate(b[i]); }\n\n function boot() {\n try {\n var target = (MESSAGES_CONTAINER && qs(document, MESSAGES_CONTAINER)) || document.body;\n sweep();\n installConversationControl();\n if (typeof MutationObserver === \"undefined\") return;\n var obs = new MutationObserver(function () { sweep(); });\n obs.observe(target, { childList: true, subtree: true });\n } catch (_) {}\n }\n})();\n"; const MD_COPY_CSS = ".cc-md-copy {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n vertical-align: middle;\n margin-left: 6px;\n}\n.cc-md-copy-btn,\n.cc-md-copy-caret {\n font: inherit;\n font-size: 11px;\n line-height: 1.4;\n padding: 1px 6px;\n color: var(--vscode-foreground);\n background: transparent;\n border: 1px solid var(--vscode-widget-border, transparent);\n border-radius: 4px;\n cursor: pointer;\n opacity: 0.65;\n}\n.cc-md-copy-btn:hover,\n.cc-md-copy-caret:hover {\n opacity: 1;\n background: var(--vscode-toolbar-hoverBackground, rgba(128, 128, 128, 0.15));\n}\n.cc-md-copy-menu {\n position: relative;\n margin-left: 4px;\n padding: 2px;\n background: var(--vscode-menu-background, var(--vscode-editorWidget-background));\n border: 1px solid var(--vscode-menu-border, var(--vscode-widget-border, transparent));\n border-radius: 4px;\n z-index: 5;\n}\n.cc-md-copy-feedback {\n margin-left: 6px;\n font-size: 11px;\n opacity: 0.85;\n color: var(--vscode-foreground);\n}\n.cc-md-copy-conversation {\n position: fixed;\n right: 16px;\n bottom: 56px;\n z-index: 10;\n padding: 2px;\n background: var(--vscode-editorWidget-background);\n border: 1px solid var(--vscode-widget-border, transparent);\n border-radius: 6px;\n opacity: 0.85;\n}\n.cc-md-copy-conversation:hover {\n opacity: 1;\n}\n"; // << Date: Wed, 10 Jun 2026 11:56:30 -1000 Subject: [PATCH 2/7] fix(md-copy): single-icon copy with real-success gating; drop plain-text + --open Fix the "says Copied but nothing copied" bug and redesign the injected controls: - copyText now reports REAL success: synchronous execCommand("copy") first (gesture-safe, secure-context-independent), Clipboard API fallback; the icon flips to a checkmark only on a genuine copy, and empty text is never success - replace the "Copy" text + caret + "Copy as plain text" menu with a single clipboard icon (Markdown only); checkmark for ~2s, no "Copied" label - decorate is idempotent (prunes duplicates) -> fixes the double rows of buttons - whole-conversation control is one gated icon (conversation view only), pinned top-right (top:26px right:4px) - cc-export.py: drop the --open flag (open raw .jsonl) per product decision - regenerate embeds into launcher/claudemax, claudemax.win.js, add-md-copy.py - tests: add tests/test_md_clipboard.py (copyText honesty: empty/exec/api/no-false -success); replace the export --open test with a rejection test; 91 tests green --- README.md | 2 +- fixes/markdown-copy-export/README.md | 16 +- fixes/markdown-copy-export/add-md-copy.py | 4 +- fixes/markdown-copy-export/cc-export.py | 14 +- fixes/markdown-copy-export/webview-inject.css | 57 ++-- fixes/markdown-copy-export/webview-inject.js | 197 ++++++++----- launcher/claudemax | 259 +++++++++++------- launcher/claudemax.win.js | 9 +- tests/test_md_clipboard.py | 93 +++++++ tests/test_md_export.py | 40 +-- 10 files changed, 430 insertions(+), 261 deletions(-) create mode 100644 tests/test_md_clipboard.py diff --git a/README.md b/README.md index 590c7fc..8530f5f 100644 --- a/README.md +++ b/README.md @@ -322,7 +322,7 @@ Setup is otherwise identical to Option 1. This is unrelated to the fixes above. | [`fixes/thinking-summaries/test-thinking-display.sh`](fixes/thinking-summaries/test-thinking-display.sh) | thinking | Live A/B test showing that the flag is the relevant lever. | | [`fixes/context-icon/fix-context-icon.py`](fixes/context-icon/fix-context-icon.py) | context icon | Option 2 standalone webview patcher with `--revert`. | | [`fixes/markdown-copy-export/add-md-copy.py`](fixes/markdown-copy-export/add-md-copy.py) | markdown copy | Standalone webview patcher (sentinel block, reverse-transform `--revert`). | -| [`fixes/markdown-copy-export/cc-export.py`](fixes/markdown-copy-export/cc-export.py) | markdown copy | Standalone session exporter (markdown/text, `--open`). | +| [`fixes/markdown-copy-export/cc-export.py`](fixes/markdown-copy-export/cc-export.py) | markdown copy | Standalone session exporter (markdown or plain text). | | [`fixes/markdown-copy-export/webview-inject.js`](fixes/markdown-copy-export/webview-inject.js) | markdown copy | Single source of the appended copy-controls IIFE. | | [`tools/gen-embeds`](tools/gen-embeds) | markdown copy | Generates the embedded payload into the launcher + patcher; `--check` drift gate. | | [`TECHNICAL.md`](TECHNICAL.md) | both | Full root-cause analysis, the reconcile model, and design notes. | diff --git a/fixes/markdown-copy-export/README.md b/fixes/markdown-copy-export/README.md index 98ae7f0..29ff44e 100644 --- a/fixes/markdown-copy-export/README.md +++ b/fixes/markdown-copy-export/README.md @@ -4,10 +4,11 @@ The Claude Code VS Code chat has no way to copy a whole message or the whole conversation as Markdown (only code blocks have a copy button). This adds a -per-message copy control (Markdown primary, plain text secondary) on every user -and assistant message, a "copy entire conversation" control, and a standalone -CLI that exports a session transcript to Markdown/plain text or opens the raw -`.jsonl`. Affects the VS Code extension webview; the CLI is independent of it. +single-click copy icon (Markdown) on every user and assistant message - it flips +to a checkmark only when the copy actually lands - plus a floating "copy +conversation" icon, and a standalone CLI that exports a session transcript to +Markdown or plain text. Affects the VS Code extension webview; the CLI is +independent of it. ## Standalone usage @@ -27,7 +28,6 @@ Session exporter (independent of the webview): python3 fixes/markdown-copy-export/cc-export.py --format text python3 fixes/markdown-copy-export/cc-export.py --include-thinking --include-tools python3 fixes/markdown-copy-export/cc-export.py --session ID -o out.md - python3 fixes/markdown-copy-export/cc-export.py --open # open raw .jsonl in VS Code ## Launcher toggle @@ -41,8 +41,10 @@ controls and reverts ours on the next launch. `CC_WORKAROUNDS=0` reverts it too. the nested, experiment+analytics-gated rating widget, which the sanitizer strips); chrome strip-prefixes `toolUse_`/`toolResult_`/`toolReference_`/`thinking_`/ `unknownContent_` plus `[data-message-rating]` and `button`; - `navigator.clipboard.writeText` (proven to work via the built-in copy-code - button). Optional refinements pinned at install: the messages container and the + clipboard write via a synchronous `document.execCommand("copy")` first + (gesture-safe and works without a secure context, e.g. remote / code-server), + falling back to `navigator.clipboard.writeText`; the icon only flips to a + checkmark when a copy actually succeeds. Optional refinements pinned at install: the messages container and the single all-content wrapper, if any (see the inject source constants `MESSAGES_CONTAINER` / `ASSISTANT_CONTENT`). When a bundle update renames the bubble anchor or a chrome hook, the Phase-9 selector guard fails loudly; re-pin diff --git a/fixes/markdown-copy-export/add-md-copy.py b/fixes/markdown-copy-export/add-md-copy.py index 619b3fc..403a8f2 100644 --- a/fixes/markdown-copy-export/add-md-copy.py +++ b/fixes/markdown-copy-export/add-md-copy.py @@ -31,8 +31,8 @@ BACKUP_SUFFIX = ".bak-md-copy" # >>>CCWA-MD-COPY-EMBED>>> (generated by tools/gen-embeds; do not edit) -INJECT_JS = base64.b64decode("LyogY2MtbWQtY29weTogcGVyLW1lc3NhZ2UgYW5kIHdob2xlLWNvbnZlcnNhdGlvbiBjb3B5IChtYXJrZG93bi9wbGFpbikgZm9yIHRoZQogKiBDbGF1ZGUgQ29kZSBWUyBDb2RlIHdlYnZpZXcuIFNlbGYtY29udGFpbmVkIElJRkUgYXBwZW5kZWQgdG8gd2Vidmlldy9pbmRleC5qcy4KICogQWRkaXRpdmUgYW5kIHJlYWQtb25seSB3LnIudC4gYXBwIHN0YXRlOyBrZXllZCBvbiBzdGFibGUgQ1NTLW1vZHVsZSBjbGFzcwogKiBwcmVmaXhlcywgc28gaXQgZmFpbHMgc2FmZSAoY29udHJvbHMgc2ltcGx5IGRvIG5vdCBhcHBlYXIpIGlmIGEgcHJlZml4IG1vdmVzLgogKiBFeHBvc2VzIGl0cyBwdXJlIGZ1bmN0aW9ucyBmb3Igbm9kZSB1bml0IHRlc3RzOyBib290KClzIG9ubHkgaW4gYSByZWFsIHdlYnZpZXcuICovCi8qIExlYWRpbmcgJzsnIHNvIHRoYXQsIGFwcGVuZGVkIGFmdGVyIHRoZSBidW5kbGUsIHRoaXMgSUlGRSBjYW4gbmV2ZXIgYmUgcGFyc2VkIGFzCiAqIGEgY2FsbCBvbiB0aGUgYnVuZGxlJ3MgZmluYWwgZXhwcmVzc2lvbiBpZiBpdCBsYWNrcyBhIHRyYWlsaW5nIHNlbWljb2xvbiAoQVNJCiAqIHNhZmV0eSBhY3Jvc3MgZXh0ZW5zaW9uIGJ1aWxkcykuICovCjsoZnVuY3Rpb24gKCkgewogICJ1c2Ugc3RyaWN0IjsKCiAgdmFyIENPTlRST0xfUFJFRklYID0gImNjLW1kLWNvcHkiOyAvLyBldmVyeSBpbmplY3RlZCBub2RlJ3MgY2xhc3Mgc3RhcnRzIHdpdGggdGhpcwogIHZhciBVU0VSX0JVQkJMRSA9ICdbY2xhc3MqPSJ1c2VyTWVzc2FnZUNvbnRhaW5lcl8iXSc7CiAgLy8gQXNzaXN0YW50IG1lc3NhZ2Ugd3JhcHBlci4gVmVyaWZpZWQgb24gMi4xLjE3MDogdGhlIHJlbmRlciBlbWl0cyBleGFjdGx5IG9uZQogIC8vIGBkYXRhLXRlc3RpZD0iYXNzaXN0YW50LW1lc3NhZ2UiYCBkaXYgcGVyIGFzc2lzdGFudCB0dXJuLCB3aXRoIHRoZSByYXRpbmcKICAvLyB3aWRnZXQgYW5kIGNvbnRlbnQgYmxvY2tzIGFzIGl0cyBjaGlsZHJlbi4gKFRoZSBlYXJsaWVyIGBbZGF0YS1tZXNzYWdlLXJhdGluZ11gCiAgLy8gd2FzIFdST05HOiB0aGF0IGF0dHJpYnV0ZSBzaXRzIG9uIHRoZSBuZXN0ZWQgcmF0aW5nIGNvbnRyb2wsIHdoaWNoIGlzIGFsc28gb25seQogIC8vIHJlbmRlcmVkIGJlaGluZCBhbiBleHBlcmltZW50K2FuYWx5dGljcyBnYXRlLikgUmUtcGlubmVkIGluIFRhc2sgNi4KICB2YXIgQVNTSVNUQU5UX0JVQkJMRSA9ICdbZGF0YS10ZXN0aWQ9ImFzc2lzdGFudC1tZXNzYWdlIl0nOwogIHZhciBNRVNTQUdFU19DT05UQUlORVIgPSAnW2NsYXNzKj0ibWVzc2FnZXNDb250YWluZXJfIl0nOyAvLyBlLmcuICdbY2xhc3MqPSJ0aW1lbGluZV8iXSc7ICIiIC0+IG9ic2VydmUgZG9jdW1lbnQuYm9keQogIC8vIE9wdGlvbmFsIG5hcnJvd2luZyBvbmx5LiBNVVNUIGJlIGEgc2luZ2xlIHdyYXBwZXIgYXJvdW5kIEFMTCBjb250ZW50IGJsb2NrcywKICAvLyBub3QgYSBwZXItYmxvY2sgY2xhc3MgKGEgdHVybiBoYXMgbXVsdGlwbGUgYmxvY2tzKS4gIiIgLT4gdXNlIHRoZSBidWJibGUgaXRzZWxmCiAgLy8gKGFscmVhZHkgYWdncmVnYXRlcyBhbGwgYmxvY2tzOyBzYW5pdGl6ZUNsb25lIGlzIHRoZSBjb3JyZWN0bmVzcyBnYXRlKS4KICB2YXIgQVNTSVNUQU5UX0NPTlRFTlQgPSAiIjsKICB2YXIgRkVFREJBQ0tfTVMgPSAxODAwOwoKICAvLyAtLS0tIEhUTUwgLT4gTWFya2Rvd24gKERPTSB3YWxrKSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgLy8gVXNlcyBvbmx5OiBub2RlVHlwZSwgdGFnTmFtZSwgY2hpbGROb2RlcywgdGV4dENvbnRlbnQsIGdldEF0dHJpYnV0ZSwgY2xhc3NOYW1lLgogIGZ1bmN0aW9uIGh0bWxUb01hcmtkb3duKHJvb3QpIHsKICAgIC8vIExvbmdlc3QgcnVuIG9mIGNvbnNlY3V0aXZlIGJhY2t0aWNrcyBpbiBzLCBzbyBhIGNvZGUgZGVsaW1pdGVyL2ZlbmNlIGNhbiBiZQogICAgLy8gY2hvc2VuIGxvbmdlciB0aGFuIGFueXRoaW5nIGluc2lkZSBpdCAoZWxzZSBgYGAgaW4gdGhlIGNvbnRlbnQgY2xvc2VzIGVhcmx5KS4KICAgIGZ1bmN0aW9uIGJhY2t0aWNrUnVuKHMpIHsKICAgICAgdmFyIG1heCA9IDAsIGN1ciA9IDA7CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgcy5sZW5ndGg7IGkrKykgewogICAgICAgIGlmIChzLmNoYXJBdChpKSA9PT0gImAiKSB7IGN1cisrOyBpZiAoY3VyID4gbWF4KSBtYXggPSBjdXI7IH0gZWxzZSBjdXIgPSAwOwogICAgICB9CiAgICAgIHJldHVybiBtYXg7CiAgICB9CiAgICBmdW5jdGlvbiBmZW5jZShzLCBtaW4pIHsgdmFyIG4gPSBiYWNrdGlja1J1bihzKSArIDE7IGlmIChuIDwgbWluKSBuID0gbWluOyByZXR1cm4gbmV3IEFycmF5KG4gKyAxKS5qb2luKCJgIik7IH0KICAgIGZ1bmN0aW9uIGlubGluZShub2RlKSB7CiAgICAgIHZhciBvdXQgPSAiIjsKICAgICAgdmFyIGtpZHMgPSBub2RlLmNoaWxkTm9kZXMgfHwgW107CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwga2lkcy5sZW5ndGg7IGkrKykgewogICAgICAgIHZhciBjID0ga2lkc1tpXTsKICAgICAgICBpZiAoYy5ub2RlVHlwZSA9PT0gMykgeyBvdXQgKz0gYy50ZXh0Q29udGVudCB8fCAiIjsgY29udGludWU7IH0KICAgICAgICBpZiAoYy5ub2RlVHlwZSAhPT0gMSkgY29udGludWU7CiAgICAgICAgdmFyIHRhZyA9IChjLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCk7CiAgICAgICAgaWYgKHRhZyA9PT0gIkJSIikgb3V0ICs9ICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiU1RST05HIiB8fCB0YWcgPT09ICJCIikgb3V0ICs9ICIqKiIgKyBpbmxpbmUoYykgKyAiKioiOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIkVNIiB8fCB0YWcgPT09ICJJIikgb3V0ICs9ICIqIiArIGlubGluZShjKSArICIqIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJERUwiIHx8IHRhZyA9PT0gIlMiKSBvdXQgKz0gIn5+IiArIGlubGluZShjKSArICJ+fiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiQ09ERSIpIHsKICAgICAgICAgIHZhciBjdCA9IGMudGV4dENvbnRlbnQgfHwgIiI7CiAgICAgICAgICB2YXIgZCA9IGZlbmNlKGN0LCAxKTsKICAgICAgICAgIC8vIENvbW1vbk1hcmsgc3RyaXBzIG9uZSBsZWFkaW5nK3RyYWlsaW5nIHNwYWNlLCBzbyBwYWQgd2hlbiBhbiBlZGdlIGlzIGEKICAgICAgICAgIC8vIGJhY2t0aWNrIHRvIGtlZXAgaXQgZnJvbSBtZXJnaW5nIHdpdGggdGhlIGRlbGltaXRlci4KICAgICAgICAgIHZhciBwID0gKGN0LmNoYXJBdCgwKSA9PT0gImAiIHx8IGN0LmNoYXJBdChjdC5sZW5ndGggLSAxKSA9PT0gImAiKSA/ICIgIiA6ICIiOwogICAgICAgICAgb3V0ICs9IGQgKyBwICsgY3QgKyBwICsgZDsKICAgICAgICB9CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiQSIpIHsKICAgICAgICAgIHZhciBocmVmID0gYy5nZXRBdHRyaWJ1dGUgPyBjLmdldEF0dHJpYnV0ZSgiaHJlZiIpIDogbnVsbDsKICAgICAgICAgIHZhciB0ID0gaW5saW5lKGMpOwogICAgICAgICAgb3V0ICs9IGhyZWYgPyAiWyIgKyB0ICsgIl0oIiArIGhyZWYgKyAiKSIgOiB0OwogICAgICAgIH0gZWxzZSBvdXQgKz0gaW5saW5lKGMpOyAvLyB1bmtub3duIGlubGluZSB3cmFwcGVyOiBrZWVwIHRleHQsIGRyb3AgdGFnCiAgICAgIH0KICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIGZ1bmN0aW9uIGxhbmdPZihjb2RlRWwpIHsKICAgICAgdmFyIGNscyA9ICIiOwogICAgICBpZiAoY29kZUVsKSBjbHMgPSAoY29kZUVsLmdldEF0dHJpYnV0ZSAmJiBjb2RlRWwuZ2V0QXR0cmlidXRlKCJjbGFzcyIpKSB8fCBjb2RlRWwuY2xhc3NOYW1lIHx8ICIiOwogICAgICB2YXIgbSA9IC9sYW5ndWFnZS0oW0EtWmEtejAtOSsjLlwtXSspLy5leGVjKGNscyB8fCAiIik7CiAgICAgIHJldHVybiBtID8gbVsxXSA6ICIiOwogICAgfQogICAgZnVuY3Rpb24gZmluZENoaWxkVGFnKG5vZGUsIHRhZykgewogICAgICB2YXIga2lkcyA9IG5vZGUuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgaWYgKGtpZHNbaV0ubm9kZVR5cGUgPT09IDEgJiYgKGtpZHNbaV0udGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKSA9PT0gdGFnKSByZXR1cm4ga2lkc1tpXTsKICAgICAgfQogICAgICByZXR1cm4gbnVsbDsKICAgIH0KICAgIGZ1bmN0aW9uIGxpc3Qobm9kZSwgb3JkZXJlZCwgZGVwdGgpIHsKICAgICAgdmFyIG91dCA9ICIiLCBuID0gMTsKICAgICAgdmFyIGtpZHMgPSBub2RlLmNoaWxkTm9kZXMgfHwgW107CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwga2lkcy5sZW5ndGg7IGkrKykgewogICAgICAgIHZhciBsaSA9IGtpZHNbaV07CiAgICAgICAgaWYgKGxpLm5vZGVUeXBlICE9PSAxIHx8IChsaS50YWdOYW1lIHx8ICIiKS50b1VwcGVyQ2FzZSgpICE9PSAiTEkiKSBjb250aW51ZTsKICAgICAgICB2YXIgbWFya2VyID0gb3JkZXJlZCA/IG4rKyArICIuICIgOiAiLSAiOwogICAgICAgIHZhciBpbmRlbnQgPSBuZXcgQXJyYXkoZGVwdGggKyAxKS5qb2luKCIgICIpOwogICAgICAgIHZhciBsZWFkID0gIiIsIG5lc3RlZCA9ICIiOwogICAgICAgIHZhciBsayA9IGxpLmNoaWxkTm9kZXMgfHwgW107CiAgICAgICAgZm9yICh2YXIgaiA9IDA7IGogPCBsay5sZW5ndGg7IGorKykgewogICAgICAgICAgdmFyIGNoID0gbGtbal07CiAgICAgICAgICB2YXIgY3QgPSBjaC5ub2RlVHlwZSA9PT0gMSA/IChjaC50YWdOYW1lIHx8ICIiKS50b1VwcGVyQ2FzZSgpIDogIiI7CiAgICAgICAgICBpZiAoY3QgPT09ICJVTCIpIG5lc3RlZCArPSBsaXN0KGNoLCBmYWxzZSwgZGVwdGggKyAxKTsKICAgICAgICAgIGVsc2UgaWYgKGN0ID09PSAiT0wiKSBuZXN0ZWQgKz0gbGlzdChjaCwgdHJ1ZSwgZGVwdGggKyAxKTsKICAgICAgICAgIGVsc2UgaWYgKGNoLm5vZGVUeXBlID09PSAzKSBsZWFkICs9IGNoLnRleHRDb250ZW50IHx8ICIiOwogICAgICAgICAgZWxzZSBsZWFkICs9IGlubGluZShjaCk7CiAgICAgICAgfQogICAgICAgIG91dCArPSBpbmRlbnQgKyBtYXJrZXIgKyBsZWFkLnRyaW0oKSArICJcbiIgKyBuZXN0ZWQ7CiAgICAgIH0KICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIGZ1bmN0aW9uIHRhYmxlKG5vZGUpIHsKICAgICAgdmFyIHJvd3MgPSBbXTsKICAgICAgKGZ1bmN0aW9uIGNvbGxlY3QoY29udGFpbmVyKSB7CiAgICAgICAgdmFyIGtpZHMgPSBjb250YWluZXIuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGtpZHMubGVuZ3RoOyBpKyspIHsKICAgICAgICAgIHZhciBjID0ga2lkc1tpXTsKICAgICAgICAgIGlmIChjLm5vZGVUeXBlICE9PSAxKSBjb250aW51ZTsKICAgICAgICAgIHZhciB0ID0gKGMudGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKTsKICAgICAgICAgIGlmICh0ID09PSAiVEhFQUQiIHx8IHQgPT09ICJUQk9EWSIgfHwgdCA9PT0gIlRGT09UIikgY29sbGVjdChjKTsKICAgICAgICAgIGVsc2UgaWYgKHQgPT09ICJUUiIpIHsKICAgICAgICAgICAgdmFyIGNlbGxzID0gW10sIGNjID0gYy5jaGlsZE5vZGVzIHx8IFtdOwogICAgICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IGNjLmxlbmd0aDsgaisrKSB7CiAgICAgICAgICAgICAgdmFyIGQgPSBjY1tqXTsKICAgICAgICAgICAgICBpZiAoZC5ub2RlVHlwZSAhPT0gMSkgY29udGludWU7CiAgICAgICAgICAgICAgdmFyIGR0ID0gKGQudGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKTsKICAgICAgICAgICAgICBpZiAoZHQgPT09ICJUSCIgfHwgZHQgPT09ICJURCIpIGNlbGxzLnB1c2goaW5saW5lKGQpLnRyaW0oKSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgcm93cy5wdXNoKGNlbGxzKTsKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0pKG5vZGUpOwogICAgICBpZiAoIXJvd3MubGVuZ3RoKSByZXR1cm4gIiI7CiAgICAgIHZhciBoZWFkID0gcm93c1swXSwgYm9keSA9IHJvd3Muc2xpY2UoMSk7CiAgICAgIHZhciBzZXAgPSBoZWFkLm1hcChmdW5jdGlvbiAoKSB7IHJldHVybiAiLS0tIjsgfSk7CiAgICAgIHZhciBvdXQgPSAifCAiICsgaGVhZC5qb2luKCIgfCAiKSArICIgfFxufCAiICsgc2VwLmpvaW4oIiB8ICIpICsgIiB8XG4iOwogICAgICBmb3IgKHZhciBrID0gMDsgayA8IGJvZHkubGVuZ3RoOyBrKyspIG91dCArPSAifCAiICsgYm9keVtrXS5qb2luKCIgfCAiKSArICIgfFxuIjsKICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIGZ1bmN0aW9uIGJsb2NrKG5vZGUpIHsKICAgICAgdmFyIG91dCA9ICIiOwogICAgICB2YXIga2lkcyA9IG5vZGUuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgdmFyIGMgPSBraWRzW2ldOwogICAgICAgIGlmIChjLm5vZGVUeXBlID09PSAzKSB7IGlmICgoYy50ZXh0Q29udGVudCB8fCAiIikudHJpbSgpKSBvdXQgKz0gYy50ZXh0Q29udGVudDsgY29udGludWU7IH0KICAgICAgICBpZiAoYy5ub2RlVHlwZSAhPT0gMSkgY29udGludWU7CiAgICAgICAgdmFyIHRhZyA9IChjLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCk7CiAgICAgICAgaWYgKC9eSFsxLTZdJC8udGVzdCh0YWcpKSBvdXQgKz0gbmV3IEFycmF5KCt0YWdbMV0gKyAxKS5qb2luKCIjIikgKyAiICIgKyBpbmxpbmUoYykudHJpbSgpICsgIlxuXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIlAiKSBvdXQgKz0gaW5saW5lKGMpLnRyaW0oKSArICJcblxuIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJVTCIpIG91dCArPSBsaXN0KGMsIGZhbHNlLCAwKSArICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiT0wiKSBvdXQgKz0gbGlzdChjLCB0cnVlLCAwKSArICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiUFJFIikgewogICAgICAgICAgdmFyIGNvZGUgPSBmaW5kQ2hpbGRUYWcoYywgIkNPREUiKTsKICAgICAgICAgIHZhciBsYW5nID0gbGFuZ09mKGNvZGUgfHwgYyk7CiAgICAgICAgICB2YXIgYm9keSA9IChjb2RlIHx8IGMpLnRleHRDb250ZW50IHx8ICIiOwogICAgICAgICAgdmFyIGYgPSBmZW5jZShib2R5LCAzKTsKICAgICAgICAgIG91dCArPSBmICsgbGFuZyArICJcbiIgKyBib2R5LnJlcGxhY2UoL1xuJC8sICIiKSArICJcbiIgKyBmICsgIlxuXG4iOwogICAgICAgIH0gZWxzZSBpZiAodGFnID09PSAiQkxPQ0tRVU9URSIpIHsKICAgICAgICAgIHZhciBpbm5lciA9IGJsb2NrKGMpLnRyaW0oKS5zcGxpdCgiXG4iKS5tYXAoZnVuY3Rpb24gKGwpIHsgcmV0dXJuICI+ICIgKyBsOyB9KS5qb2luKCJcbiIpOwogICAgICAgICAgb3V0ICs9IGlubmVyICsgIlxuXG4iOwogICAgICAgIH0gZWxzZSBpZiAodGFnID09PSAiSFIiKSBvdXQgKz0gIi0tLVxuXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIlRBQkxFIikgb3V0ICs9IHRhYmxlKGMpICsgIlxuIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJCUiIpIG91dCArPSAiXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIlNUUk9ORyIgfHwgdGFnID09PSAiQiIgfHwgdGFnID09PSAiRU0iIHx8IHRhZyA9PT0gIkkiIHx8CiAgICAgICAgICAgICAgICAgdGFnID09PSAiQSIgfHwgdGFnID09PSAiQ09ERSIgfHwgdGFnID09PSAiREVMIiB8fCB0YWcgPT09ICJTIikKICAgICAgICAgIG91dCArPSBpbmxpbmUoYykgKyAiXG5cbiI7CiAgICAgICAgZWxzZSBvdXQgKz0gYmxvY2soYyk7IC8vIHVua25vd24gd3JhcHBlcjogcmVjdXJzZSAoZHJvcCB0YWcsIGtlZXAgY29udGVudCkKICAgICAgfQogICAgICByZXR1cm4gb3V0OwogICAgfQogICAgLy8gYmxvY2soKSBkaXNwYXRjaGVzIG9uIGVhY2ggQ0hJTEQncyB0YWcsIHRyZWF0aW5nIHRoZSBwYXNzZWQgbm9kZSBhcyBhIHBsYWluCiAgICAvLyBjb250YWluZXIuIFdyYXAgcm9vdCBpbiBhIG9uZS1vZmYgY29udGFpbmVyIHNvIHJvb3QncyBPV04gdGFnIGlzIGRpc3BhdGNoZWQKICAgIC8vIHRvbzogY2FsbGVycyBwYXNzIGVpdGhlciB0aGUgYnViYmxlIGNvbnRhaW5lciAoaXRzIGJsb2NrIGNoaWxkcmVuIHJlbmRlcikgb3IKICAgIC8vIGEgc2luZ2xlIGJsb2NrIGVsZW1lbnQgbGlrZSA8cHJlPi88dWw+Lzx0YWJsZT4gKG5vdyBoYW5kbGVkLCBub3QgZmxhdHRlbmVkKS4KICAgIHJldHVybiBibG9jayh7IGNoaWxkTm9kZXM6IFtyb290XSB9KS5yZXBsYWNlKC9cbnszLH0vZywgIlxuXG4iKS50cmltKCk7CiAgfQoKICAvLyAtLS0tIHB1cmUgaGVscGVycyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgZnVuY3Rpb24gaGFzUHJlZml4KG5vZGUsIHByZWZpeCkgewogICAgaWYgKG5vZGUubm9kZVR5cGUgIT09IDEgfHwgdHlwZW9mIG5vZGUuY2xhc3NOYW1lICE9PSAic3RyaW5nIikgcmV0dXJuIGZhbHNlOwogICAgdmFyIHBhcnRzID0gbm9kZS5jbGFzc05hbWUuc3BsaXQoL1xzKy8pOwogICAgZm9yICh2YXIgaSA9IDA7IGkgPCBwYXJ0cy5sZW5ndGg7IGkrKykgaWYgKHBhcnRzW2ldLmluZGV4T2YocHJlZml4KSA9PT0gMCkgcmV0dXJuIHRydWU7CiAgICByZXR1cm4gZmFsc2U7CiAgfQoKICAvLyBDbGFzcy1wcmVmaXggaG9va3MgZm9yIG5vbi1jb250ZW50IGNocm9tZSB0aGF0IHJlbmRlcnMgKmluc2lkZSogYW4gYXNzaXN0YW50CiAgLy8gYnViYmxlICh2ZXJpZmllZCBvbiAyLjEuMTcwOyBUYXNrIDYgcmUtcGlucyB0aGVzZSkuIHRvb2wqL3RoaW5raW5nXyBhcmUgdGhlIHYxCiAgLy8gZXhjbHVzaW9uczsgdW5rbm93bkNvbnRlbnRfIGlzIHRoZSByZW5kZXJlcidzIGZhbGxiYWNrIGZvciB1bnJlY29nbml6ZWQgYmxvY2sKICAvLyB0eXBlcywgc28gc3RyaXBwaW5nIGl0IG1ha2VzIGEgKmZ1dHVyZSogYmxvY2sgdHlwZSBmYWlsIHNhZmUgdG8gZXhjbHVkZWQgcmF0aGVyCiAgLy8gdGhhbiBsZWFraW5nICJVbnN1cHBvcnRlZCBjb250ZW50IiBpbnRvIHRoZSBjb3B5LiBSZS1waW4gaWYgYSBwcmVmaXggbW92ZXMuCiAgdmFyIENIUk9NRV9QUkVGSVhFUyA9IFsidG9vbFVzZV8iLCAidG9vbFJlc3VsdF8iLCAidG9vbFJlZmVyZW5jZV8iLCAidGhpbmtpbmdfIiwgInVua25vd25Db250ZW50XyJdOwoKICAvLyBUcnVlIGZvciBhbnkgbm9kZSB0aGF0IG11c3QgbmV2ZXIgYXBwZWFyIGluIGNvcGllZCBvdXRwdXQ6IG91ciBvd24gY29udHJvbHMsCiAgLy8gdGhlIHJhdGluZyB3aWRnZXQgKGBkYXRhLW1lc3NhZ2UtcmF0aW5nYCArIGl0cyAiVGhhbmtzIGZvciB5b3VyIGZlZWRiYWNrIgogIC8vIHRleHQpLCBhbnkgYnV0dG9uIChjb3B5LWNvZGUgY2hyb21lKSwgYW5kIHRoZSBleGNsdWRlZCBjb250ZW50IGJsb2NrcyBhYm92ZS4KICBmdW5jdGlvbiBpc0Nocm9tZShub2RlKSB7CiAgICBpZiAobm9kZS5ub2RlVHlwZSAhPT0gMSkgcmV0dXJuIGZhbHNlOwogICAgaWYgKChub2RlLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCkgPT09ICJCVVRUT04iKSByZXR1cm4gdHJ1ZTsKICAgIGlmIChub2RlLmdldEF0dHJpYnV0ZSAmJiBub2RlLmdldEF0dHJpYnV0ZSgiZGF0YS1tZXNzYWdlLXJhdGluZyIpICE9PSBudWxsKSByZXR1cm4gdHJ1ZTsKICAgIGlmIChoYXNQcmVmaXgobm9kZSwgQ09OVFJPTF9QUkVGSVgpKSByZXR1cm4gdHJ1ZTsKICAgIGZvciAodmFyIGkgPSAwOyBpIDwgQ0hST01FX1BSRUZJWEVTLmxlbmd0aDsgaSsrKSBpZiAoaGFzUHJlZml4KG5vZGUsIENIUk9NRV9QUkVGSVhFU1tpXSkpIHJldHVybiB0cnVlOwogICAgcmV0dXJuIGZhbHNlOwogIH0KCiAgLy8gRGVlcC1jbG9uZSBgY29udGVudE5vZGVgLCB0aGVuIHN0cmlwIGV2ZXJ5IGNocm9tZSBub2RlIHNvIGNvcGllZCBvdXRwdXQgaXMgdGhlCiAgLy8gbWVzc2FnZSdzIHRleHQgY29udGVudCBvbmx5LiBUaGlzIGlzIGEgQ09SUkVDVE5FU1MgR0FURSwgbm90IGNvc21ldGljOiB0aGUKICAvLyBkZWZhdWx0IGNvbnRlbnQgbm9kZSBpcyB0aGUgd2hvbGUgYnViYmxlIChhbGwgY29udGVudC1ibG9jayBzaWJsaW5ncywgc28gbXVsdGktCiAgLy8gYmxvY2sgYXNzaXN0YW50IHR1cm5zIGFyZSBjYXB0dXJlZCksIGFuZCB0aGlzIHN0cmlwLWxpc3QgaXMgdGhlIG9ubHkgdGhpbmcKICAvLyBrZWVwaW5nIHRoZSByYXRpbmcgd2lkZ2V0IGFuZCB2MS1leGNsdWRlZCBibG9ja3Mgb3V0IG9mIHRoZSBjb3B5LgogIGZ1bmN0aW9uIHNhbml0aXplQ2xvbmUoY29udGVudE5vZGUpIHsKICAgIHZhciBjbG9uZSA9IGNvbnRlbnROb2RlLmNsb25lTm9kZSh0cnVlKTsKICAgIChmdW5jdGlvbiBzdHJpcChub2RlKSB7CiAgICAgIHZhciBraWRzID0gKG5vZGUuY2hpbGROb2RlcyB8fCBbXSkuc2xpY2UoKTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgdmFyIGMgPSBraWRzW2ldOwogICAgICAgIGlmIChjLm5vZGVUeXBlID09PSAxICYmIGlzQ2hyb21lKGMpKSB7IG5vZGUucmVtb3ZlQ2hpbGQoYyk7IGNvbnRpbnVlOyB9CiAgICAgICAgaWYgKGMubm9kZVR5cGUgPT09IDEpIHN0cmlwKGMpOwogICAgICB9CiAgICB9KShjbG9uZSk7CiAgICByZXR1cm4gY2xvbmU7CiAgfQoKICBmdW5jdGlvbiBjbGFzc2lmeUJ1YmJsZShub2RlKSB7CiAgICBpZiAobm9kZS5ub2RlVHlwZSAhPT0gMSkgcmV0dXJuIG51bGw7CiAgICBpZiAoaGFzUHJlZml4KG5vZGUsICJ1c2VyTWVzc2FnZUNvbnRhaW5lcl8iKSkgcmV0dXJuICJ1c2VyIjsKICAgIGlmIChub2RlLmdldEF0dHJpYnV0ZSAmJiBub2RlLmdldEF0dHJpYnV0ZSgiZGF0YS10ZXN0aWQiKSA9PT0gImFzc2lzdGFudC1tZXNzYWdlIikgcmV0dXJuICJhc3Npc3RhbnQiOwogICAgcmV0dXJuIG51bGw7CiAgfQoKICAvLyBCdWlsZCB0aGUgd2hvbGUtY29udmVyc2F0aW9uIG1hcmtkb3duIGZyb20gYW4gb3JkZXJlZCBsaXN0IG9mIGJ1YmJsZXMuCiAgLy8gYGNvbnRlbnRPZihidWJibGUpYCByZXNvbHZlcyB0aGUgY29udGVudCBub2RlIChkZWZhdWx0OiB0aGUgYnViYmxlIGl0c2VsZiwgc28KICAvLyBldmVyeSBjb250ZW50IGJsb2NrIGlzIGluY2x1ZGVkOyBzYW5pdGl6ZUNsb25lIGRyb3BzIGNocm9tZSk7IGEgZGVmYXVsdCBpcwogIC8vIHByb3ZpZGVkIGZvciB0ZXN0cy4KICBmdW5jdGlvbiBjb252ZXJzYXRpb25Ub01hcmtkb3duKGJ1YmJsZXMsIGNvbnRlbnRPZikgewogICAgY29udGVudE9mID0gY29udGVudE9mIHx8IGZ1bmN0aW9uIChiKSB7IHJldHVybiBiOyB9OwogICAgdmFyIHBhcnRzID0gW107CiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGJ1YmJsZXMubGVuZ3RoOyBpKyspIHsKICAgICAgdmFyIHJvbGUgPSBjbGFzc2lmeUJ1YmJsZShidWJibGVzW2ldKTsKICAgICAgaWYgKCFyb2xlKSBjb250aW51ZTsKICAgICAgdmFyIGNsZWFuID0gc2FuaXRpemVDbG9uZShjb250ZW50T2YoYnViYmxlc1tpXSkpOwogICAgICB2YXIgYm9keSA9IHJvbGUgPT09ICJhc3Npc3RhbnQiID8gaHRtbFRvTWFya2Rvd24oY2xlYW4pIDogKGNsZWFuLnRleHRDb250ZW50IHx8ICIiKS50cmltKCk7CiAgICAgIGlmICghYm9keSkgY29udGludWU7CiAgICAgIHBhcnRzLnB1c2goKHJvbGUgPT09ICJ1c2VyIiA/ICIjIyBVc2VyIiA6ICIjIyBBc3Npc3RhbnQiKSArICJcblxuIiArIGJvZHkpOwogICAgfQogICAgcmV0dXJuIHBhcnRzLmpvaW4oIlxuXG4iKSArIChwYXJ0cy5sZW5ndGggPyAiXG4iIDogIiIpOwogIH0KCiAgLy8gLS0tLSBleHBvcnRzIChub2RlIHRlc3RzKSAvIGJvb3QgKHJlYWwgd2VidmlldykgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogIGlmICh0eXBlb2YgZG9jdW1lbnQgIT09ICJ1bmRlZmluZWQiKSB7CiAgICBib290KCk7CiAgfSBlbHNlIGlmICh0eXBlb2YgbW9kdWxlICE9PSAidW5kZWZpbmVkIiAmJiBtb2R1bGUuZXhwb3J0cykgewogICAgbW9kdWxlLmV4cG9ydHMgPSB7IGh0bWxUb01hcmtkb3duOiBodG1sVG9NYXJrZG93biwgc2FuaXRpemVDbG9uZTogc2FuaXRpemVDbG9uZSwKICAgICAgICAgICAgICAgICAgICAgICBjbGFzc2lmeUJ1YmJsZTogY2xhc3NpZnlCdWJibGUsIGNvbnZlcnNhdGlvblRvTWFya2Rvd246IGNvbnZlcnNhdGlvblRvTWFya2Rvd24gfTsKICB9CgogIC8vIC0tLS0gbGl2ZS13ZWJ2aWV3IHdpcmluZyAocnVucyBvbmx5IHdoZW4gYSBkb2N1bWVudCBleGlzdHMpIC0tLS0tLS0tLS0tLS0tLS0KICBmdW5jdGlvbiBxcyhub2RlLCBzZWwpIHsgdHJ5IHsgcmV0dXJuIHNlbCAmJiBub2RlLnF1ZXJ5U2VsZWN0b3IgPyBub2RlLnF1ZXJ5U2VsZWN0b3Ioc2VsKSA6IG51bGw7IH0gY2F0Y2ggKF8pIHsgcmV0dXJuIG51bGw7IH0gfQogIGZ1bmN0aW9uIHFzYShzZWwpIHsgdHJ5IHsgcmV0dXJuIEFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoc2VsKSk7IH0gY2F0Y2ggKF8pIHsgcmV0dXJuIFtdOyB9IH0KCiAgLy8gVGhlIGNvbnRlbnQgbm9kZSB0byBjb252ZXJ0L2NvcHk6IHRoZSBvcHRpb25hbCBBU1NJU1RBTlRfQ09OVEVOVCB3cmFwcGVyIGlmCiAgLy8gcGlubmVkIGFuZCBwcmVzZW50LCBlbHNlIHRoZSBidWJibGUgaXRzZWxmLiBUaGUgYnViYmxlIGFscmVhZHkgY29udGFpbnMgZXZlcnkKICAvLyBjb250ZW50LWJsb2NrIHNpYmxpbmcgb2YgYSBtdWx0aS1ibG9jayB0dXJuLCBhbmQgc2FuaXRpemVDbG9uZSBzdHJpcHMgdGhlCiAgLy8gY2hyb21lIChyYXRpbmcgd2lkZ2V0LCB0b29sL3RoaW5raW5nL3Vua25vd24gYmxvY2tzLCBidXR0b25zLCBvdXIgY29udHJvbHMpCiAgLy8gZWl0aGVyIHdheSAtLSBzbyB0aGlzIGlzIGEgbmFycm93aW5nLCBuZXZlciB0aGUgdGhpbmcgdGhhdCBndWFyYW50ZWVzCiAgLy8gY29ycmVjdG5lc3MuCiAgZnVuY3Rpb24gY29udGVudE5vZGVPZihidWJibGUsIHJvbGUpIHsKICAgIGlmIChyb2xlID09PSAiYXNzaXN0YW50IiAmJiBBU1NJU1RBTlRfQ09OVEVOVCkgewogICAgICB2YXIgbiA9IHFzKGJ1YmJsZSwgQVNTSVNUQU5UX0NPTlRFTlQpOwogICAgICBpZiAobikgcmV0dXJuIG47CiAgICB9CiAgICByZXR1cm4gYnViYmxlOwogIH0KCiAgZnVuY3Rpb24gY29weVRleHQodGV4dCkgewogICAgdHJ5IHsKICAgICAgaWYgKG5hdmlnYXRvci5jbGlwYm9hcmQgJiYgbmF2aWdhdG9yLmNsaXBib2FyZC53cml0ZVRleHQpIHJldHVybiBuYXZpZ2F0b3IuY2xpcGJvYXJkLndyaXRlVGV4dCh0ZXh0KTsKICAgIH0gY2F0Y2ggKF8pIHt9CiAgICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKCk7IC8vIGJlc3QtZWZmb3J0OyBuZXZlciB0aHJvdyBpbnRvIHRoZSBhcHAKICB9CgogIGZ1bmN0aW9uIGZsYXNoRmVlZGJhY2soaG9zdCkgewogICAgdHJ5IHsKICAgICAgdmFyIGZiID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic3BhbiIpOwogICAgICBmYi5jbGFzc05hbWUgPSBDT05UUk9MX1BSRUZJWCArICItZmVlZGJhY2siOwogICAgICBmYi50ZXh0Q29udGVudCA9ICJDb3BpZWQiOwogICAgICBob3N0LmFwcGVuZENoaWxkKGZiKTsKICAgICAgc2V0VGltZW91dChmdW5jdGlvbiAoKSB7IGlmIChmYiAmJiBmYi5wYXJlbnROb2RlKSBmYi5wYXJlbnROb2RlLnJlbW92ZUNoaWxkKGZiKTsgfSwgRkVFREJBQ0tfTVMpOwogICAgfSBjYXRjaCAoXykge30KICB9CgogIGZ1bmN0aW9uIGJ1YmJsZU1hcmtkb3duKGJ1YmJsZSwgcm9sZSkgewogICAgdmFyIGNsZWFuID0gc2FuaXRpemVDbG9uZShjb250ZW50Tm9kZU9mKGJ1YmJsZSwgcm9sZSkpOwogICAgcmV0dXJuIHJvbGUgPT09ICJhc3Npc3RhbnQiID8gaHRtbFRvTWFya2Rvd24oY2xlYW4pIDogKGNsZWFuLnRleHRDb250ZW50IHx8ICIiKS50cmltKCk7CiAgfQogIGZ1bmN0aW9uIGJ1YmJsZVBsYWluKGJ1YmJsZSwgcm9sZSkgewogICAgcmV0dXJuIChzYW5pdGl6ZUNsb25lKGNvbnRlbnROb2RlT2YoYnViYmxlLCByb2xlKSkudGV4dENvbnRlbnQgfHwgIiIpLnRyaW0oKTsKICB9CgogIC8vIEJ1aWxkIGEgc2luZ2xlIGNvbnRyb2w6IGEgcHJpbWFyeSAiQ29weSIgKG1hcmtkb3duKSBwbHVzIGEgc21hbGwgY2FyZXQgdGhhdAogIC8vIHRvZ2dsZXMgYSBtZW51IHdpdGggIkNvcHkgYXMgcGxhaW4gdGV4dCIuIEFsbCBub2RlcyBjYXJyeSB0aGUgQ09OVFJPTF9QUkVGSVgKICAvLyBjbGFzcyBzbyBzYW5pdGl6ZUNsb25lIHJlbW92ZXMgdGhlbSBmcm9tIGFueSBjb3BpZWQgY29udGVudC4KICBmdW5jdGlvbiBidWlsZENvbnRyb2wob25NYXJrZG93biwgb25QbGFpbikgewogICAgdmFyIHdyYXAgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzcGFuIik7CiAgICB3cmFwLmNsYXNzTmFtZSA9IENPTlRST0xfUFJFRklYOwogICAgdmFyIHByaW1hcnkgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJidXR0b24iKTsKICAgIHByaW1hcnkudHlwZSA9ICJidXR0b24iOwogICAgcHJpbWFyeS5jbGFzc05hbWUgPSBDT05UUk9MX1BSRUZJWCArICItYnRuIjsKICAgIHByaW1hcnkudGl0bGUgPSAiQ29weSBhcyBNYXJrZG93biI7CiAgICBwcmltYXJ5LnRleHRDb250ZW50ID0gIkNvcHkiOwogICAgcHJpbWFyeS5hZGRFdmVudExpc3RlbmVyKCJjbGljayIsIGZ1bmN0aW9uIChlKSB7IGUuc3RvcFByb3BhZ2F0aW9uKCk7IG9uTWFya2Rvd24ocHJpbWFyeSk7IH0pOwogICAgdmFyIGNhcmV0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiYnV0dG9uIik7CiAgICBjYXJldC50eXBlID0gImJ1dHRvbiI7CiAgICBjYXJldC5jbGFzc05hbWUgPSBDT05UUk9MX1BSRUZJWCArICItY2FyZXQiOwogICAgY2FyZXQudGl0bGUgPSAiQ29weSBvcHRpb25zIjsKICAgIGNhcmV0LnRleHRDb250ZW50ID0gIuKWviI7IC8vIGJsYWNrIGRvd24tcG9pbnRpbmcgc21hbGwgdHJpYW5nbGUKICAgIHZhciBtZW51ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic3BhbiIpOwogICAgbWVudS5jbGFzc05hbWUgPSBDT05UUk9MX1BSRUZJWCArICItbWVudSI7CiAgICBtZW51LnN0eWxlLmRpc3BsYXkgPSAibm9uZSI7CiAgICB2YXIgcGxhaW4gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJidXR0b24iKTsKICAgIHBsYWluLnR5cGUgPSAiYnV0dG9uIjsKICAgIHBsYWluLmNsYXNzTmFtZSA9IENPTlRST0xfUFJFRklYICsgIi1idG4iOwogICAgcGxhaW4udGV4dENvbnRlbnQgPSAiQ29weSBhcyBwbGFpbiB0ZXh0IjsKICAgIHBsYWluLmFkZEV2ZW50TGlzdGVuZXIoImNsaWNrIiwgZnVuY3Rpb24gKGUpIHsgZS5zdG9wUHJvcGFnYXRpb24oKTsgbWVudS5zdHlsZS5kaXNwbGF5ID0gIm5vbmUiOyBvblBsYWluKHBsYWluKTsgfSk7CiAgICBtZW51LmFwcGVuZENoaWxkKHBsYWluKTsKICAgIGNhcmV0LmFkZEV2ZW50TGlzdGVuZXIoImNsaWNrIiwgZnVuY3Rpb24gKGUpIHsKICAgICAgZS5zdG9wUHJvcGFnYXRpb24oKTsKICAgICAgbWVudS5zdHlsZS5kaXNwbGF5ID0gbWVudS5zdHlsZS5kaXNwbGF5ID09PSAibm9uZSIgPyAiaW5saW5lLWJsb2NrIiA6ICJub25lIjsKICAgIH0pOwogICAgd3JhcC5hcHBlbmRDaGlsZChwcmltYXJ5KTsKICAgIHdyYXAuYXBwZW5kQ2hpbGQoY2FyZXQpOwogICAgd3JhcC5hcHBlbmRDaGlsZChtZW51KTsKICAgIHJldHVybiB3cmFwOwogIH0KCiAgZnVuY3Rpb24gZGVjb3JhdGUoYnViYmxlKSB7CiAgICB0cnkgewogICAgICB2YXIgcm9sZSA9IGNsYXNzaWZ5QnViYmxlKGJ1YmJsZSk7CiAgICAgIGlmICghcm9sZSkgcmV0dXJuOwogICAgICBpZiAocXMoYnViYmxlLCAiLiIgKyBDT05UUk9MX1BSRUZJWCkpIHJldHVybjsgLy8gYWxyZWFkeSBkZWNvcmF0ZWQKICAgICAgdmFyIGNvbnRyb2wgPSBidWlsZENvbnRyb2woCiAgICAgICAgZnVuY3Rpb24gKGhvc3QpIHsgY29weVRleHQoYnViYmxlTWFya2Rvd24oYnViYmxlLCByb2xlKSkudGhlbihmdW5jdGlvbiAoKSB7IGZsYXNoRmVlZGJhY2soY29udHJvbCk7IH0pOyB9LAogICAgICAgIGZ1bmN0aW9uIChob3N0KSB7IGNvcHlUZXh0KGJ1YmJsZVBsYWluKGJ1YmJsZSwgcm9sZSkpLnRoZW4oZnVuY3Rpb24gKCkgeyBmbGFzaEZlZWRiYWNrKGNvbnRyb2wpOyB9KTsgfQogICAgICApOwogICAgICBidWJibGUuYXBwZW5kQ2hpbGQoY29udHJvbCk7CiAgICB9IGNhdGNoIChfKSB7fQogIH0KCiAgZnVuY3Rpb24gY29weUNvbnZlcnNhdGlvbihmb3JtYXQpIHsKICAgIHZhciBidWJibGVzID0gcXNhKFVTRVJfQlVCQkxFICsgIiwiICsgQVNTSVNUQU5UX0JVQkJMRSk7CiAgICBpZiAoZm9ybWF0ID09PSAidGV4dCIpIHsKICAgICAgdmFyIGxpbmVzID0gW107CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgYnViYmxlcy5sZW5ndGg7IGkrKykgewogICAgICAgIHZhciByb2xlID0gY2xhc3NpZnlCdWJibGUoYnViYmxlc1tpXSk7CiAgICAgICAgaWYgKCFyb2xlKSBjb250aW51ZTsKICAgICAgICB2YXIgYm9keSA9IGJ1YmJsZVBsYWluKGJ1YmJsZXNbaV0sIHJvbGUpOwogICAgICAgIGlmIChib2R5KSBsaW5lcy5wdXNoKGJvZHkpOwogICAgICB9CiAgICAgIHJldHVybiBjb3B5VGV4dChsaW5lcy5qb2luKCJcblxuIikgKyAobGluZXMubGVuZ3RoID8gIlxuIiA6ICIiKSk7CiAgICB9CiAgICByZXR1cm4gY29weVRleHQoY29udmVyc2F0aW9uVG9NYXJrZG93bihidWJibGVzLCBmdW5jdGlvbiAoYikgewogICAgICByZXR1cm4gY29udGVudE5vZGVPZihiLCBjbGFzc2lmeUJ1YmJsZShiKSk7CiAgICB9KSk7CiAgfQoKICBmdW5jdGlvbiBpbnN0YWxsQ29udmVyc2F0aW9uQ29udHJvbCgpIHsKICAgIHRyeSB7CiAgICAgIGlmIChxcyhkb2N1bWVudCwgIi4iICsgQ09OVFJPTF9QUkVGSVggKyAiLWNvbnZlcnNhdGlvbiIpKSByZXR1cm47CiAgICAgIHZhciBiYXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKTsKICAgICAgYmFyLmNsYXNzTmFtZSA9IENPTlRST0xfUFJFRklYICsgIi1jb252ZXJzYXRpb24iOwogICAgICB2YXIgY29udHJvbCA9IGJ1aWxkQ29udHJvbCgKICAgICAgICBmdW5jdGlvbiAoKSB7IGNvcHlDb252ZXJzYXRpb24oIm1hcmtkb3duIikudGhlbihmdW5jdGlvbiAoKSB7IGZsYXNoRmVlZGJhY2soYmFyKTsgfSk7IH0sCiAgICAgICAgZnVuY3Rpb24gKCkgeyBjb3B5Q29udmVyc2F0aW9uKCJ0ZXh0IikudGhlbihmdW5jdGlvbiAoKSB7IGZsYXNoRmVlZGJhY2soYmFyKTsgfSk7IH0KICAgICAgKTsKICAgICAgY29udHJvbC50aXRsZSA9ICJDb3B5IGVudGlyZSBjb252ZXJzYXRpb24iOwogICAgICBiYXIuYXBwZW5kQ2hpbGQoY29udHJvbCk7CiAgICAgIGRvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQoYmFyKTsgLy8gZml4ZWQtcG9zaXRpb24gdmlhIENTUzsgcGxhY2VtZW50IHJlZmluZWQgaW4gVGFzayA2CiAgICB9IGNhdGNoIChfKSB7fQogIH0KCiAgZnVuY3Rpb24gc3dlZXAoKSB7IHZhciBiID0gcXNhKFVTRVJfQlVCQkxFICsgIiwiICsgQVNTSVNUQU5UX0JVQkJMRSk7IGZvciAodmFyIGkgPSAwOyBpIDwgYi5sZW5ndGg7IGkrKykgZGVjb3JhdGUoYltpXSk7IH0KCiAgZnVuY3Rpb24gYm9vdCgpIHsKICAgIHRyeSB7CiAgICAgIHZhciB0YXJnZXQgPSAoTUVTU0FHRVNfQ09OVEFJTkVSICYmIHFzKGRvY3VtZW50LCBNRVNTQUdFU19DT05UQUlORVIpKSB8fCBkb2N1bWVudC5ib2R5OwogICAgICBzd2VlcCgpOwogICAgICBpbnN0YWxsQ29udmVyc2F0aW9uQ29udHJvbCgpOwogICAgICBpZiAodHlwZW9mIE11dGF0aW9uT2JzZXJ2ZXIgPT09ICJ1bmRlZmluZWQiKSByZXR1cm47CiAgICAgIHZhciBvYnMgPSBuZXcgTXV0YXRpb25PYnNlcnZlcihmdW5jdGlvbiAoKSB7IHN3ZWVwKCk7IH0pOwogICAgICBvYnMub2JzZXJ2ZSh0YXJnZXQsIHsgY2hpbGRMaXN0OiB0cnVlLCBzdWJ0cmVlOiB0cnVlIH0pOwogICAgfSBjYXRjaCAoXykge30KICB9Cn0pKCk7Cg==").decode("utf-8") -INJECT_CSS = base64.b64decode("LmNjLW1kLWNvcHkgewogIGRpc3BsYXk6IGlubGluZS1mbGV4OwogIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgZ2FwOiAycHg7CiAgdmVydGljYWwtYWxpZ246IG1pZGRsZTsKICBtYXJnaW4tbGVmdDogNnB4Owp9Ci5jYy1tZC1jb3B5LWJ0biwKLmNjLW1kLWNvcHktY2FyZXQgewogIGZvbnQ6IGluaGVyaXQ7CiAgZm9udC1zaXplOiAxMXB4OwogIGxpbmUtaGVpZ2h0OiAxLjQ7CiAgcGFkZGluZzogMXB4IDZweDsKICBjb2xvcjogdmFyKC0tdnNjb2RlLWZvcmVncm91bmQpOwogIGJhY2tncm91bmQ6IHRyYW5zcGFyZW50OwogIGJvcmRlcjogMXB4IHNvbGlkIHZhcigtLXZzY29kZS13aWRnZXQtYm9yZGVyLCB0cmFuc3BhcmVudCk7CiAgYm9yZGVyLXJhZGl1czogNHB4OwogIGN1cnNvcjogcG9pbnRlcjsKICBvcGFjaXR5OiAwLjY1Owp9Ci5jYy1tZC1jb3B5LWJ0bjpob3ZlciwKLmNjLW1kLWNvcHktY2FyZXQ6aG92ZXIgewogIG9wYWNpdHk6IDE7CiAgYmFja2dyb3VuZDogdmFyKC0tdnNjb2RlLXRvb2xiYXItaG92ZXJCYWNrZ3JvdW5kLCByZ2JhKDEyOCwgMTI4LCAxMjgsIDAuMTUpKTsKfQouY2MtbWQtY29weS1tZW51IHsKICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgbWFyZ2luLWxlZnQ6IDRweDsKICBwYWRkaW5nOiAycHg7CiAgYmFja2dyb3VuZDogdmFyKC0tdnNjb2RlLW1lbnUtYmFja2dyb3VuZCwgdmFyKC0tdnNjb2RlLWVkaXRvcldpZGdldC1iYWNrZ3JvdW5kKSk7CiAgYm9yZGVyOiAxcHggc29saWQgdmFyKC0tdnNjb2RlLW1lbnUtYm9yZGVyLCB2YXIoLS12c2NvZGUtd2lkZ2V0LWJvcmRlciwgdHJhbnNwYXJlbnQpKTsKICBib3JkZXItcmFkaXVzOiA0cHg7CiAgei1pbmRleDogNTsKfQouY2MtbWQtY29weS1mZWVkYmFjayB7CiAgbWFyZ2luLWxlZnQ6IDZweDsKICBmb250LXNpemU6IDExcHg7CiAgb3BhY2l0eTogMC44NTsKICBjb2xvcjogdmFyKC0tdnNjb2RlLWZvcmVncm91bmQpOwp9Ci5jYy1tZC1jb3B5LWNvbnZlcnNhdGlvbiB7CiAgcG9zaXRpb246IGZpeGVkOwogIHJpZ2h0OiAxNnB4OwogIGJvdHRvbTogNTZweDsKICB6LWluZGV4OiAxMDsKICBwYWRkaW5nOiAycHg7CiAgYmFja2dyb3VuZDogdmFyKC0tdnNjb2RlLWVkaXRvcldpZGdldC1iYWNrZ3JvdW5kKTsKICBib3JkZXI6IDFweCBzb2xpZCB2YXIoLS12c2NvZGUtd2lkZ2V0LWJvcmRlciwgdHJhbnNwYXJlbnQpOwogIGJvcmRlci1yYWRpdXM6IDZweDsKICBvcGFjaXR5OiAwLjg1Owp9Ci5jYy1tZC1jb3B5LWNvbnZlcnNhdGlvbjpob3ZlciB7CiAgb3BhY2l0eTogMTsKfQo=").decode("utf-8") +INJECT_JS = base64.b64decode("LyogY2MtbWQtY29weTogcGVyLW1lc3NhZ2UgYW5kIHdob2xlLWNvbnZlcnNhdGlvbiBjb3B5IChNYXJrZG93bikgZm9yIHRoZQogKiBDbGF1ZGUgQ29kZSBWUyBDb2RlIHdlYnZpZXcuIFNlbGYtY29udGFpbmVkIElJRkUgYXBwZW5kZWQgdG8gd2Vidmlldy9pbmRleC5qcy4KICogRWFjaCBjb250cm9sIGlzIGEgc2luZ2xlIGNsaXBib2FyZCBpY29uIHRoYXQgZmxpcHMgdG8gYSBjaGVja21hcmsgZm9yIH4ycyB3aGVuIGEKICogY29weSBhY3R1YWxseSBzdWNjZWVkcyAobm8gdGV4dCBsYWJlbCwgbm8gbWVudSkuIEFkZGl0aXZlIGFuZCByZWFkLW9ubHkgdy5yLnQuCiAqIGFwcCBzdGF0ZTsga2V5ZWQgb24gc3RhYmxlIENTUy1tb2R1bGUgY2xhc3MgcHJlZml4ZXMsIHNvIGl0IGZhaWxzIHNhZmUgKGNvbnRyb2xzCiAqIHNpbXBseSBkbyBub3QgYXBwZWFyKSBpZiBhIHByZWZpeCBtb3Zlcy4KICogRXhwb3NlcyBpdHMgcHVyZSBmdW5jdGlvbnMgZm9yIG5vZGUgdW5pdCB0ZXN0czsgYm9vdCgpcyBvbmx5IGluIGEgcmVhbCB3ZWJ2aWV3LiAqLwovKiBMZWFkaW5nICc7JyBzbyB0aGF0LCBhcHBlbmRlZCBhZnRlciB0aGUgYnVuZGxlLCB0aGlzIElJRkUgY2FuIG5ldmVyIGJlIHBhcnNlZCBhcwogKiBhIGNhbGwgb24gdGhlIGJ1bmRsZSdzIGZpbmFsIGV4cHJlc3Npb24gaWYgaXQgbGFja3MgYSB0cmFpbGluZyBzZW1pY29sb24gKEFTSQogKiBzYWZldHkgYWNyb3NzIGV4dGVuc2lvbiBidWlsZHMpLiAqLwo7KGZ1bmN0aW9uICgpIHsKICAidXNlIHN0cmljdCI7CgogIHZhciBDT05UUk9MX1BSRUZJWCA9ICJjYy1tZC1jb3B5IjsgLy8gZXZlcnkgaW5qZWN0ZWQgbm9kZSdzIGNsYXNzIHN0YXJ0cyB3aXRoIHRoaXMKICB2YXIgVVNFUl9CVUJCTEUgPSAnW2NsYXNzKj0idXNlck1lc3NhZ2VDb250YWluZXJfIl0nOwogIC8vIEFzc2lzdGFudCBtZXNzYWdlIHdyYXBwZXIuIFZlcmlmaWVkIG9uIDIuMS4xNzA6IHRoZSByZW5kZXIgZW1pdHMgZXhhY3RseSBvbmUKICAvLyBgZGF0YS10ZXN0aWQ9ImFzc2lzdGFudC1tZXNzYWdlImAgZGl2IHBlciBhc3Npc3RhbnQgdHVybiwgd2l0aCB0aGUgcmF0aW5nCiAgLy8gd2lkZ2V0IGFuZCBjb250ZW50IGJsb2NrcyBhcyBpdHMgY2hpbGRyZW4uIChUaGUgZWFybGllciBgW2RhdGEtbWVzc2FnZS1yYXRpbmddYAogIC8vIHdhcyBXUk9ORzogdGhhdCBhdHRyaWJ1dGUgc2l0cyBvbiB0aGUgbmVzdGVkIHJhdGluZyBjb250cm9sLCB3aGljaCBpcyBhbHNvIG9ubHkKICAvLyByZW5kZXJlZCBiZWhpbmQgYW4gZXhwZXJpbWVudCthbmFseXRpY3MgZ2F0ZS4pIFJlLXBpbm5lZCBpbiBUYXNrIDYuCiAgdmFyIEFTU0lTVEFOVF9CVUJCTEUgPSAnW2RhdGEtdGVzdGlkPSJhc3Npc3RhbnQtbWVzc2FnZSJdJzsKICB2YXIgTUVTU0FHRVNfQ09OVEFJTkVSID0gJ1tjbGFzcyo9Im1lc3NhZ2VzQ29udGFpbmVyXyJdJzsgLy8gZS5nLiAnW2NsYXNzKj0idGltZWxpbmVfIl0nOyAiIiAtPiBvYnNlcnZlIGRvY3VtZW50LmJvZHkKICAvLyBPcHRpb25hbCBuYXJyb3dpbmcgb25seS4gTVVTVCBiZSBhIHNpbmdsZSB3cmFwcGVyIGFyb3VuZCBBTEwgY29udGVudCBibG9ja3MsCiAgLy8gbm90IGEgcGVyLWJsb2NrIGNsYXNzIChhIHR1cm4gaGFzIG11bHRpcGxlIGJsb2NrcykuICIiIC0+IHVzZSB0aGUgYnViYmxlIGl0c2VsZgogIC8vIChhbHJlYWR5IGFnZ3JlZ2F0ZXMgYWxsIGJsb2Nrczsgc2FuaXRpemVDbG9uZSBpcyB0aGUgY29ycmVjdG5lc3MgZ2F0ZSkuCiAgdmFyIEFTU0lTVEFOVF9DT05URU5UID0gIiI7CiAgdmFyIEZFRURCQUNLX01TID0gMjAwMDsgLy8gaG93IGxvbmcgdGhlIGNoZWNrbWFyayBzaG93cyBhZnRlciBhIHN1Y2Nlc3NmdWwgY29weQoKICAvLyAtLS0tIEhUTUwgLT4gTWFya2Rvd24gKERPTSB3YWxrKSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgLy8gVXNlcyBvbmx5OiBub2RlVHlwZSwgdGFnTmFtZSwgY2hpbGROb2RlcywgdGV4dENvbnRlbnQsIGdldEF0dHJpYnV0ZSwgY2xhc3NOYW1lLgogIGZ1bmN0aW9uIGh0bWxUb01hcmtkb3duKHJvb3QpIHsKICAgIC8vIExvbmdlc3QgcnVuIG9mIGNvbnNlY3V0aXZlIGJhY2t0aWNrcyBpbiBzLCBzbyBhIGNvZGUgZGVsaW1pdGVyL2ZlbmNlIGNhbiBiZQogICAgLy8gY2hvc2VuIGxvbmdlciB0aGFuIGFueXRoaW5nIGluc2lkZSBpdCAoZWxzZSBgYGAgaW4gdGhlIGNvbnRlbnQgY2xvc2VzIGVhcmx5KS4KICAgIGZ1bmN0aW9uIGJhY2t0aWNrUnVuKHMpIHsKICAgICAgdmFyIG1heCA9IDAsIGN1ciA9IDA7CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgcy5sZW5ndGg7IGkrKykgewogICAgICAgIGlmIChzLmNoYXJBdChpKSA9PT0gImAiKSB7IGN1cisrOyBpZiAoY3VyID4gbWF4KSBtYXggPSBjdXI7IH0gZWxzZSBjdXIgPSAwOwogICAgICB9CiAgICAgIHJldHVybiBtYXg7CiAgICB9CiAgICBmdW5jdGlvbiBmZW5jZShzLCBtaW4pIHsgdmFyIG4gPSBiYWNrdGlja1J1bihzKSArIDE7IGlmIChuIDwgbWluKSBuID0gbWluOyByZXR1cm4gbmV3IEFycmF5KG4gKyAxKS5qb2luKCJgIik7IH0KICAgIGZ1bmN0aW9uIGlubGluZShub2RlKSB7CiAgICAgIHZhciBvdXQgPSAiIjsKICAgICAgdmFyIGtpZHMgPSBub2RlLmNoaWxkTm9kZXMgfHwgW107CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwga2lkcy5sZW5ndGg7IGkrKykgewogICAgICAgIHZhciBjID0ga2lkc1tpXTsKICAgICAgICBpZiAoYy5ub2RlVHlwZSA9PT0gMykgeyBvdXQgKz0gYy50ZXh0Q29udGVudCB8fCAiIjsgY29udGludWU7IH0KICAgICAgICBpZiAoYy5ub2RlVHlwZSAhPT0gMSkgY29udGludWU7CiAgICAgICAgdmFyIHRhZyA9IChjLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCk7CiAgICAgICAgaWYgKHRhZyA9PT0gIkJSIikgb3V0ICs9ICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiU1RST05HIiB8fCB0YWcgPT09ICJCIikgb3V0ICs9ICIqKiIgKyBpbmxpbmUoYykgKyAiKioiOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIkVNIiB8fCB0YWcgPT09ICJJIikgb3V0ICs9ICIqIiArIGlubGluZShjKSArICIqIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJERUwiIHx8IHRhZyA9PT0gIlMiKSBvdXQgKz0gIn5+IiArIGlubGluZShjKSArICJ+fiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiQ09ERSIpIHsKICAgICAgICAgIHZhciBjdCA9IGMudGV4dENvbnRlbnQgfHwgIiI7CiAgICAgICAgICB2YXIgZCA9IGZlbmNlKGN0LCAxKTsKICAgICAgICAgIC8vIENvbW1vbk1hcmsgc3RyaXBzIG9uZSBsZWFkaW5nK3RyYWlsaW5nIHNwYWNlLCBzbyBwYWQgd2hlbiBhbiBlZGdlIGlzIGEKICAgICAgICAgIC8vIGJhY2t0aWNrIHRvIGtlZXAgaXQgZnJvbSBtZXJnaW5nIHdpdGggdGhlIGRlbGltaXRlci4KICAgICAgICAgIHZhciBwID0gKGN0LmNoYXJBdCgwKSA9PT0gImAiIHx8IGN0LmNoYXJBdChjdC5sZW5ndGggLSAxKSA9PT0gImAiKSA/ICIgIiA6ICIiOwogICAgICAgICAgb3V0ICs9IGQgKyBwICsgY3QgKyBwICsgZDsKICAgICAgICB9CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiQSIpIHsKICAgICAgICAgIHZhciBocmVmID0gYy5nZXRBdHRyaWJ1dGUgPyBjLmdldEF0dHJpYnV0ZSgiaHJlZiIpIDogbnVsbDsKICAgICAgICAgIHZhciB0ID0gaW5saW5lKGMpOwogICAgICAgICAgb3V0ICs9IGhyZWYgPyAiWyIgKyB0ICsgIl0oIiArIGhyZWYgKyAiKSIgOiB0OwogICAgICAgIH0gZWxzZSBvdXQgKz0gaW5saW5lKGMpOyAvLyB1bmtub3duIGlubGluZSB3cmFwcGVyOiBrZWVwIHRleHQsIGRyb3AgdGFnCiAgICAgIH0KICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIGZ1bmN0aW9uIGxhbmdPZihjb2RlRWwpIHsKICAgICAgdmFyIGNscyA9ICIiOwogICAgICBpZiAoY29kZUVsKSBjbHMgPSAoY29kZUVsLmdldEF0dHJpYnV0ZSAmJiBjb2RlRWwuZ2V0QXR0cmlidXRlKCJjbGFzcyIpKSB8fCBjb2RlRWwuY2xhc3NOYW1lIHx8ICIiOwogICAgICB2YXIgbSA9IC9sYW5ndWFnZS0oW0EtWmEtejAtOSsjLlwtXSspLy5leGVjKGNscyB8fCAiIik7CiAgICAgIHJldHVybiBtID8gbVsxXSA6ICIiOwogICAgfQogICAgZnVuY3Rpb24gZmluZENoaWxkVGFnKG5vZGUsIHRhZykgewogICAgICB2YXIga2lkcyA9IG5vZGUuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgaWYgKGtpZHNbaV0ubm9kZVR5cGUgPT09IDEgJiYgKGtpZHNbaV0udGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKSA9PT0gdGFnKSByZXR1cm4ga2lkc1tpXTsKICAgICAgfQogICAgICByZXR1cm4gbnVsbDsKICAgIH0KICAgIGZ1bmN0aW9uIGxpc3Qobm9kZSwgb3JkZXJlZCwgZGVwdGgpIHsKICAgICAgdmFyIG91dCA9ICIiLCBuID0gMTsKICAgICAgdmFyIGtpZHMgPSBub2RlLmNoaWxkTm9kZXMgfHwgW107CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwga2lkcy5sZW5ndGg7IGkrKykgewogICAgICAgIHZhciBsaSA9IGtpZHNbaV07CiAgICAgICAgaWYgKGxpLm5vZGVUeXBlICE9PSAxIHx8IChsaS50YWdOYW1lIHx8ICIiKS50b1VwcGVyQ2FzZSgpICE9PSAiTEkiKSBjb250aW51ZTsKICAgICAgICB2YXIgbWFya2VyID0gb3JkZXJlZCA/IG4rKyArICIuICIgOiAiLSAiOwogICAgICAgIHZhciBpbmRlbnQgPSBuZXcgQXJyYXkoZGVwdGggKyAxKS5qb2luKCIgICIpOwogICAgICAgIHZhciBsZWFkID0gIiIsIG5lc3RlZCA9ICIiOwogICAgICAgIHZhciBsayA9IGxpLmNoaWxkTm9kZXMgfHwgW107CiAgICAgICAgZm9yICh2YXIgaiA9IDA7IGogPCBsay5sZW5ndGg7IGorKykgewogICAgICAgICAgdmFyIGNoID0gbGtbal07CiAgICAgICAgICB2YXIgY3QgPSBjaC5ub2RlVHlwZSA9PT0gMSA/IChjaC50YWdOYW1lIHx8ICIiKS50b1VwcGVyQ2FzZSgpIDogIiI7CiAgICAgICAgICBpZiAoY3QgPT09ICJVTCIpIG5lc3RlZCArPSBsaXN0KGNoLCBmYWxzZSwgZGVwdGggKyAxKTsKICAgICAgICAgIGVsc2UgaWYgKGN0ID09PSAiT0wiKSBuZXN0ZWQgKz0gbGlzdChjaCwgdHJ1ZSwgZGVwdGggKyAxKTsKICAgICAgICAgIGVsc2UgaWYgKGNoLm5vZGVUeXBlID09PSAzKSBsZWFkICs9IGNoLnRleHRDb250ZW50IHx8ICIiOwogICAgICAgICAgZWxzZSBsZWFkICs9IGlubGluZShjaCk7CiAgICAgICAgfQogICAgICAgIG91dCArPSBpbmRlbnQgKyBtYXJrZXIgKyBsZWFkLnRyaW0oKSArICJcbiIgKyBuZXN0ZWQ7CiAgICAgIH0KICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIGZ1bmN0aW9uIHRhYmxlKG5vZGUpIHsKICAgICAgdmFyIHJvd3MgPSBbXTsKICAgICAgKGZ1bmN0aW9uIGNvbGxlY3QoY29udGFpbmVyKSB7CiAgICAgICAgdmFyIGtpZHMgPSBjb250YWluZXIuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGtpZHMubGVuZ3RoOyBpKyspIHsKICAgICAgICAgIHZhciBjID0ga2lkc1tpXTsKICAgICAgICAgIGlmIChjLm5vZGVUeXBlICE9PSAxKSBjb250aW51ZTsKICAgICAgICAgIHZhciB0ID0gKGMudGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKTsKICAgICAgICAgIGlmICh0ID09PSAiVEhFQUQiIHx8IHQgPT09ICJUQk9EWSIgfHwgdCA9PT0gIlRGT09UIikgY29sbGVjdChjKTsKICAgICAgICAgIGVsc2UgaWYgKHQgPT09ICJUUiIpIHsKICAgICAgICAgICAgdmFyIGNlbGxzID0gW10sIGNjID0gYy5jaGlsZE5vZGVzIHx8IFtdOwogICAgICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IGNjLmxlbmd0aDsgaisrKSB7CiAgICAgICAgICAgICAgdmFyIGQgPSBjY1tqXTsKICAgICAgICAgICAgICBpZiAoZC5ub2RlVHlwZSAhPT0gMSkgY29udGludWU7CiAgICAgICAgICAgICAgdmFyIGR0ID0gKGQudGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKTsKICAgICAgICAgICAgICBpZiAoZHQgPT09ICJUSCIgfHwgZHQgPT09ICJURCIpIGNlbGxzLnB1c2goaW5saW5lKGQpLnRyaW0oKSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgcm93cy5wdXNoKGNlbGxzKTsKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0pKG5vZGUpOwogICAgICBpZiAoIXJvd3MubGVuZ3RoKSByZXR1cm4gIiI7CiAgICAgIHZhciBoZWFkID0gcm93c1swXSwgYm9keSA9IHJvd3Muc2xpY2UoMSk7CiAgICAgIHZhciBzZXAgPSBoZWFkLm1hcChmdW5jdGlvbiAoKSB7IHJldHVybiAiLS0tIjsgfSk7CiAgICAgIHZhciBvdXQgPSAifCAiICsgaGVhZC5qb2luKCIgfCAiKSArICIgfFxufCAiICsgc2VwLmpvaW4oIiB8ICIpICsgIiB8XG4iOwogICAgICBmb3IgKHZhciBrID0gMDsgayA8IGJvZHkubGVuZ3RoOyBrKyspIG91dCArPSAifCAiICsgYm9keVtrXS5qb2luKCIgfCAiKSArICIgfFxuIjsKICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIGZ1bmN0aW9uIGJsb2NrKG5vZGUpIHsKICAgICAgdmFyIG91dCA9ICIiOwogICAgICB2YXIga2lkcyA9IG5vZGUuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgdmFyIGMgPSBraWRzW2ldOwogICAgICAgIGlmIChjLm5vZGVUeXBlID09PSAzKSB7IGlmICgoYy50ZXh0Q29udGVudCB8fCAiIikudHJpbSgpKSBvdXQgKz0gYy50ZXh0Q29udGVudDsgY29udGludWU7IH0KICAgICAgICBpZiAoYy5ub2RlVHlwZSAhPT0gMSkgY29udGludWU7CiAgICAgICAgdmFyIHRhZyA9IChjLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCk7CiAgICAgICAgaWYgKC9eSFsxLTZdJC8udGVzdCh0YWcpKSBvdXQgKz0gbmV3IEFycmF5KCt0YWdbMV0gKyAxKS5qb2luKCIjIikgKyAiICIgKyBpbmxpbmUoYykudHJpbSgpICsgIlxuXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIlAiKSBvdXQgKz0gaW5saW5lKGMpLnRyaW0oKSArICJcblxuIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJVTCIpIG91dCArPSBsaXN0KGMsIGZhbHNlLCAwKSArICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiT0wiKSBvdXQgKz0gbGlzdChjLCB0cnVlLCAwKSArICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiUFJFIikgewogICAgICAgICAgdmFyIGNvZGUgPSBmaW5kQ2hpbGRUYWcoYywgIkNPREUiKTsKICAgICAgICAgIHZhciBsYW5nID0gbGFuZ09mKGNvZGUgfHwgYyk7CiAgICAgICAgICB2YXIgYm9keSA9IChjb2RlIHx8IGMpLnRleHRDb250ZW50IHx8ICIiOwogICAgICAgICAgdmFyIGYgPSBmZW5jZShib2R5LCAzKTsKICAgICAgICAgIG91dCArPSBmICsgbGFuZyArICJcbiIgKyBib2R5LnJlcGxhY2UoL1xuJC8sICIiKSArICJcbiIgKyBmICsgIlxuXG4iOwogICAgICAgIH0gZWxzZSBpZiAodGFnID09PSAiQkxPQ0tRVU9URSIpIHsKICAgICAgICAgIHZhciBpbm5lciA9IGJsb2NrKGMpLnRyaW0oKS5zcGxpdCgiXG4iKS5tYXAoZnVuY3Rpb24gKGwpIHsgcmV0dXJuICI+ICIgKyBsOyB9KS5qb2luKCJcbiIpOwogICAgICAgICAgb3V0ICs9IGlubmVyICsgIlxuXG4iOwogICAgICAgIH0gZWxzZSBpZiAodGFnID09PSAiSFIiKSBvdXQgKz0gIi0tLVxuXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIlRBQkxFIikgb3V0ICs9IHRhYmxlKGMpICsgIlxuIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJCUiIpIG91dCArPSAiXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIlNUUk9ORyIgfHwgdGFnID09PSAiQiIgfHwgdGFnID09PSAiRU0iIHx8IHRhZyA9PT0gIkkiIHx8CiAgICAgICAgICAgICAgICAgdGFnID09PSAiQSIgfHwgdGFnID09PSAiQ09ERSIgfHwgdGFnID09PSAiREVMIiB8fCB0YWcgPT09ICJTIikKICAgICAgICAgIG91dCArPSBpbmxpbmUoYykgKyAiXG5cbiI7CiAgICAgICAgZWxzZSBvdXQgKz0gYmxvY2soYyk7IC8vIHVua25vd24gd3JhcHBlcjogcmVjdXJzZSAoZHJvcCB0YWcsIGtlZXAgY29udGVudCkKICAgICAgfQogICAgICByZXR1cm4gb3V0OwogICAgfQogICAgLy8gYmxvY2soKSBkaXNwYXRjaGVzIG9uIGVhY2ggQ0hJTEQncyB0YWcsIHRyZWF0aW5nIHRoZSBwYXNzZWQgbm9kZSBhcyBhIHBsYWluCiAgICAvLyBjb250YWluZXIuIFdyYXAgcm9vdCBpbiBhIG9uZS1vZmYgY29udGFpbmVyIHNvIHJvb3QncyBPV04gdGFnIGlzIGRpc3BhdGNoZWQKICAgIC8vIHRvbzogY2FsbGVycyBwYXNzIGVpdGhlciB0aGUgYnViYmxlIGNvbnRhaW5lciAoaXRzIGJsb2NrIGNoaWxkcmVuIHJlbmRlcikgb3IKICAgIC8vIGEgc2luZ2xlIGJsb2NrIGVsZW1lbnQgbGlrZSA8cHJlPi88dWw+Lzx0YWJsZT4gKG5vdyBoYW5kbGVkLCBub3QgZmxhdHRlbmVkKS4KICAgIHJldHVybiBibG9jayh7IGNoaWxkTm9kZXM6IFtyb290XSB9KS5yZXBsYWNlKC9cbnszLH0vZywgIlxuXG4iKS50cmltKCk7CiAgfQoKICAvLyAtLS0tIHB1cmUgaGVscGVycyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgZnVuY3Rpb24gaGFzUHJlZml4KG5vZGUsIHByZWZpeCkgewogICAgaWYgKG5vZGUubm9kZVR5cGUgIT09IDEgfHwgdHlwZW9mIG5vZGUuY2xhc3NOYW1lICE9PSAic3RyaW5nIikgcmV0dXJuIGZhbHNlOwogICAgdmFyIHBhcnRzID0gbm9kZS5jbGFzc05hbWUuc3BsaXQoL1xzKy8pOwogICAgZm9yICh2YXIgaSA9IDA7IGkgPCBwYXJ0cy5sZW5ndGg7IGkrKykgaWYgKHBhcnRzW2ldLmluZGV4T2YocHJlZml4KSA9PT0gMCkgcmV0dXJuIHRydWU7CiAgICByZXR1cm4gZmFsc2U7CiAgfQoKICAvLyBDbGFzcy1wcmVmaXggaG9va3MgZm9yIG5vbi1jb250ZW50IGNocm9tZSB0aGF0IHJlbmRlcnMgKmluc2lkZSogYW4gYXNzaXN0YW50CiAgLy8gYnViYmxlICh2ZXJpZmllZCBvbiAyLjEuMTcwOyBUYXNrIDYgcmUtcGlucyB0aGVzZSkuIHRvb2wqL3RoaW5raW5nXyBhcmUgdGhlIHYxCiAgLy8gZXhjbHVzaW9uczsgdW5rbm93bkNvbnRlbnRfIGlzIHRoZSByZW5kZXJlcidzIGZhbGxiYWNrIGZvciB1bnJlY29nbml6ZWQgYmxvY2sKICAvLyB0eXBlcywgc28gc3RyaXBwaW5nIGl0IG1ha2VzIGEgKmZ1dHVyZSogYmxvY2sgdHlwZSBmYWlsIHNhZmUgdG8gZXhjbHVkZWQgcmF0aGVyCiAgLy8gdGhhbiBsZWFraW5nICJVbnN1cHBvcnRlZCBjb250ZW50IiBpbnRvIHRoZSBjb3B5LiBSZS1waW4gaWYgYSBwcmVmaXggbW92ZXMuCiAgdmFyIENIUk9NRV9QUkVGSVhFUyA9IFsidG9vbFVzZV8iLCAidG9vbFJlc3VsdF8iLCAidG9vbFJlZmVyZW5jZV8iLCAidGhpbmtpbmdfIiwgInVua25vd25Db250ZW50XyJdOwoKICAvLyBUcnVlIGZvciBhbnkgbm9kZSB0aGF0IG11c3QgbmV2ZXIgYXBwZWFyIGluIGNvcGllZCBvdXRwdXQ6IG91ciBvd24gY29udHJvbHMsCiAgLy8gdGhlIHJhdGluZyB3aWRnZXQgKGBkYXRhLW1lc3NhZ2UtcmF0aW5nYCArIGl0cyAiVGhhbmtzIGZvciB5b3VyIGZlZWRiYWNrIgogIC8vIHRleHQpLCBhbnkgYnV0dG9uIChjb3B5LWNvZGUgY2hyb21lKSwgYW5kIHRoZSBleGNsdWRlZCBjb250ZW50IGJsb2NrcyBhYm92ZS4KICBmdW5jdGlvbiBpc0Nocm9tZShub2RlKSB7CiAgICBpZiAobm9kZS5ub2RlVHlwZSAhPT0gMSkgcmV0dXJuIGZhbHNlOwogICAgaWYgKChub2RlLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCkgPT09ICJCVVRUT04iKSByZXR1cm4gdHJ1ZTsKICAgIGlmIChub2RlLmdldEF0dHJpYnV0ZSAmJiBub2RlLmdldEF0dHJpYnV0ZSgiZGF0YS1tZXNzYWdlLXJhdGluZyIpICE9PSBudWxsKSByZXR1cm4gdHJ1ZTsKICAgIGlmIChoYXNQcmVmaXgobm9kZSwgQ09OVFJPTF9QUkVGSVgpKSByZXR1cm4gdHJ1ZTsKICAgIGZvciAodmFyIGkgPSAwOyBpIDwgQ0hST01FX1BSRUZJWEVTLmxlbmd0aDsgaSsrKSBpZiAoaGFzUHJlZml4KG5vZGUsIENIUk9NRV9QUkVGSVhFU1tpXSkpIHJldHVybiB0cnVlOwogICAgcmV0dXJuIGZhbHNlOwogIH0KCiAgLy8gRGVlcC1jbG9uZSBgY29udGVudE5vZGVgLCB0aGVuIHN0cmlwIGV2ZXJ5IGNocm9tZSBub2RlIHNvIGNvcGllZCBvdXRwdXQgaXMgdGhlCiAgLy8gbWVzc2FnZSdzIHRleHQgY29udGVudCBvbmx5LiBUaGlzIGlzIGEgQ09SUkVDVE5FU1MgR0FURSwgbm90IGNvc21ldGljOiB0aGUKICAvLyBkZWZhdWx0IGNvbnRlbnQgbm9kZSBpcyB0aGUgd2hvbGUgYnViYmxlIChhbGwgY29udGVudC1ibG9jayBzaWJsaW5ncywgc28gbXVsdGktCiAgLy8gYmxvY2sgYXNzaXN0YW50IHR1cm5zIGFyZSBjYXB0dXJlZCksIGFuZCB0aGlzIHN0cmlwLWxpc3QgaXMgdGhlIG9ubHkgdGhpbmcKICAvLyBrZWVwaW5nIHRoZSByYXRpbmcgd2lkZ2V0IGFuZCB2MS1leGNsdWRlZCBibG9ja3Mgb3V0IG9mIHRoZSBjb3B5LgogIGZ1bmN0aW9uIHNhbml0aXplQ2xvbmUoY29udGVudE5vZGUpIHsKICAgIHZhciBjbG9uZSA9IGNvbnRlbnROb2RlLmNsb25lTm9kZSh0cnVlKTsKICAgIChmdW5jdGlvbiBzdHJpcChub2RlKSB7CiAgICAgIHZhciBraWRzID0gKG5vZGUuY2hpbGROb2RlcyB8fCBbXSkuc2xpY2UoKTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgdmFyIGMgPSBraWRzW2ldOwogICAgICAgIGlmIChjLm5vZGVUeXBlID09PSAxICYmIGlzQ2hyb21lKGMpKSB7IG5vZGUucmVtb3ZlQ2hpbGQoYyk7IGNvbnRpbnVlOyB9CiAgICAgICAgaWYgKGMubm9kZVR5cGUgPT09IDEpIHN0cmlwKGMpOwogICAgICB9CiAgICB9KShjbG9uZSk7CiAgICByZXR1cm4gY2xvbmU7CiAgfQoKICBmdW5jdGlvbiBjbGFzc2lmeUJ1YmJsZShub2RlKSB7CiAgICBpZiAobm9kZS5ub2RlVHlwZSAhPT0gMSkgcmV0dXJuIG51bGw7CiAgICBpZiAoaGFzUHJlZml4KG5vZGUsICJ1c2VyTWVzc2FnZUNvbnRhaW5lcl8iKSkgcmV0dXJuICJ1c2VyIjsKICAgIGlmIChub2RlLmdldEF0dHJpYnV0ZSAmJiBub2RlLmdldEF0dHJpYnV0ZSgiZGF0YS10ZXN0aWQiKSA9PT0gImFzc2lzdGFudC1tZXNzYWdlIikgcmV0dXJuICJhc3Npc3RhbnQiOwogICAgcmV0dXJuIG51bGw7CiAgfQoKICAvLyBCdWlsZCB0aGUgd2hvbGUtY29udmVyc2F0aW9uIG1hcmtkb3duIGZyb20gYW4gb3JkZXJlZCBsaXN0IG9mIGJ1YmJsZXMuCiAgLy8gYGNvbnRlbnRPZihidWJibGUpYCByZXNvbHZlcyB0aGUgY29udGVudCBub2RlIChkZWZhdWx0OiB0aGUgYnViYmxlIGl0c2VsZiwgc28KICAvLyBldmVyeSBjb250ZW50IGJsb2NrIGlzIGluY2x1ZGVkOyBzYW5pdGl6ZUNsb25lIGRyb3BzIGNocm9tZSk7IGEgZGVmYXVsdCBpcwogIC8vIHByb3ZpZGVkIGZvciB0ZXN0cy4KICBmdW5jdGlvbiBjb252ZXJzYXRpb25Ub01hcmtkb3duKGJ1YmJsZXMsIGNvbnRlbnRPZikgewogICAgY29udGVudE9mID0gY29udGVudE9mIHx8IGZ1bmN0aW9uIChiKSB7IHJldHVybiBiOyB9OwogICAgdmFyIHBhcnRzID0gW107CiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGJ1YmJsZXMubGVuZ3RoOyBpKyspIHsKICAgICAgdmFyIHJvbGUgPSBjbGFzc2lmeUJ1YmJsZShidWJibGVzW2ldKTsKICAgICAgaWYgKCFyb2xlKSBjb250aW51ZTsKICAgICAgdmFyIGNsZWFuID0gc2FuaXRpemVDbG9uZShjb250ZW50T2YoYnViYmxlc1tpXSkpOwogICAgICB2YXIgYm9keSA9IHJvbGUgPT09ICJhc3Npc3RhbnQiID8gaHRtbFRvTWFya2Rvd24oY2xlYW4pIDogKGNsZWFuLnRleHRDb250ZW50IHx8ICIiKS50cmltKCk7CiAgICAgIGlmICghYm9keSkgY29udGludWU7CiAgICAgIHBhcnRzLnB1c2goKHJvbGUgPT09ICJ1c2VyIiA/ICIjIyBVc2VyIiA6ICIjIyBBc3Npc3RhbnQiKSArICJcblxuIiArIGJvZHkpOwogICAgfQogICAgcmV0dXJuIHBhcnRzLmpvaW4oIlxuXG4iKSArIChwYXJ0cy5sZW5ndGggPyAiXG4iIDogIiIpOwogIH0KCiAgLy8gLS0tLSBleHBvcnRzIChub2RlIHRlc3RzKSAvIGJvb3QgKHJlYWwgd2VidmlldykgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogIGlmICh0eXBlb2YgZG9jdW1lbnQgIT09ICJ1bmRlZmluZWQiKSB7CiAgICBib290KCk7CiAgfSBlbHNlIGlmICh0eXBlb2YgbW9kdWxlICE9PSAidW5kZWZpbmVkIiAmJiBtb2R1bGUuZXhwb3J0cykgewogICAgbW9kdWxlLmV4cG9ydHMgPSB7IGh0bWxUb01hcmtkb3duOiBodG1sVG9NYXJrZG93biwgc2FuaXRpemVDbG9uZTogc2FuaXRpemVDbG9uZSwKICAgICAgICAgICAgICAgICAgICAgICBjbGFzc2lmeUJ1YmJsZTogY2xhc3NpZnlCdWJibGUsIGNvbnZlcnNhdGlvblRvTWFya2Rvd246IGNvbnZlcnNhdGlvblRvTWFya2Rvd24sCiAgICAgICAgICAgICAgICAgICAgICAgY29weVRleHQ6IGNvcHlUZXh0IH07CiAgfQoKICAvLyAtLS0tIGxpdmUtd2VidmlldyB3aXJpbmcgKHJ1bnMgb25seSB3aGVuIGEgZG9jdW1lbnQgZXhpc3RzKSAtLS0tLS0tLS0tLS0tLS0tCiAgZnVuY3Rpb24gcXMobm9kZSwgc2VsKSB7IHRyeSB7IHJldHVybiBzZWwgJiYgbm9kZS5xdWVyeVNlbGVjdG9yID8gbm9kZS5xdWVyeVNlbGVjdG9yKHNlbCkgOiBudWxsOyB9IGNhdGNoIChfKSB7IHJldHVybiBudWxsOyB9IH0KICBmdW5jdGlvbiBxc2Eoc2VsKSB7IHRyeSB7IHJldHVybiBBcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKHNlbCkpOyB9IGNhdGNoIChfKSB7IHJldHVybiBbXTsgfSB9CgogIC8vIFRoZSBjb250ZW50IG5vZGUgdG8gY29udmVydC9jb3B5OiB0aGUgb3B0aW9uYWwgQVNTSVNUQU5UX0NPTlRFTlQgd3JhcHBlciBpZgogIC8vIHBpbm5lZCBhbmQgcHJlc2VudCwgZWxzZSB0aGUgYnViYmxlIGl0c2VsZi4gVGhlIGJ1YmJsZSBhbHJlYWR5IGNvbnRhaW5zIGV2ZXJ5CiAgLy8gY29udGVudC1ibG9jayBzaWJsaW5nIG9mIGEgbXVsdGktYmxvY2sgdHVybiwgYW5kIHNhbml0aXplQ2xvbmUgc3RyaXBzIHRoZQogIC8vIGNocm9tZSAocmF0aW5nIHdpZGdldCwgdG9vbC90aGlua2luZy91bmtub3duIGJsb2NrcywgYnV0dG9ucywgb3VyIGNvbnRyb2xzKQogIC8vIGVpdGhlciB3YXkgLS0gc28gdGhpcyBpcyBhIG5hcnJvd2luZywgbmV2ZXIgdGhlIHRoaW5nIHRoYXQgZ3VhcmFudGVlcwogIC8vIGNvcnJlY3RuZXNzLgogIGZ1bmN0aW9uIGNvbnRlbnROb2RlT2YoYnViYmxlLCByb2xlKSB7CiAgICBpZiAocm9sZSA9PT0gImFzc2lzdGFudCIgJiYgQVNTSVNUQU5UX0NPTlRFTlQpIHsKICAgICAgdmFyIG4gPSBxcyhidWJibGUsIEFTU0lTVEFOVF9DT05URU5UKTsKICAgICAgaWYgKG4pIHJldHVybiBuOwogICAgfQogICAgcmV0dXJuIGJ1YmJsZTsKICB9CgogIC8vIENvcHkgYHNgIHZpYSBhIHN5bmNocm9ub3VzIGV4ZWNDb21tYW5kKCJjb3B5Iikgb24gYW4gb2ZmLXNjcmVlbiB0ZXh0YXJlYSwgYW5kCiAgLy8gcmVwb3J0IHdoZXRoZXIgaXQgYWN0dWFsbHkgaGFwcGVuZWQuIERvbmUgZmlyc3QgKGFuZCBzeW5jaHJvbm91c2x5KSBiZWNhdXNlIGl0CiAgLy8gcnVucyBpbnNpZGUgdGhlIGNsaWNrIGdlc3R1cmUgYW5kIHdvcmtzIHdoZXRoZXIgb3Igbm90IHRoZSBwYWdlIGlzIGEgc2VjdXJlCiAgLy8gY29udGV4dCAtLSBzbyBpdCBjb3ZlcnMgcmVtb3RlIC8gY29kZS1zZXJ2ZXIsIHdoZXJlIHRoZSBhc3luYyBDbGlwYm9hcmQgQVBJIGlzCiAgLy8gc2ltcGx5IGFic2VudC4gUmVzdG9yZXMgdGhlIHByaW9yIHNlbGVjdGlvbi9mb2N1cyBzbyBpdCBpcyBpbnZpc2libGUuCiAgZnVuY3Rpb24gZXhlY0NvcHkocykgewogICAgdHJ5IHsKICAgICAgaWYgKHR5cGVvZiBkb2N1bWVudCA9PT0gInVuZGVmaW5lZCIgfHwgIWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQpIHJldHVybiBmYWxzZTsKICAgICAgdmFyIHByZXYgPSBkb2N1bWVudC5hY3RpdmVFbGVtZW50IHx8IG51bGw7CiAgICAgIHZhciBzZWwgPSBkb2N1bWVudC5nZXRTZWxlY3Rpb24gPyBkb2N1bWVudC5nZXRTZWxlY3Rpb24oKSA6IG51bGw7CiAgICAgIHZhciBzYXZlZCA9IChzZWwgJiYgc2VsLnJhbmdlQ291bnQpID8gc2VsLmdldFJhbmdlQXQoMCkgOiBudWxsOwogICAgICB2YXIgdGEgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJ0ZXh0YXJlYSIpOwogICAgICB0YS52YWx1ZSA9IHM7CiAgICAgIHRhLnNldEF0dHJpYnV0ZSgicmVhZG9ubHkiLCAiIik7CiAgICAgIHRhLnN0eWxlLnBvc2l0aW9uID0gImZpeGVkIjsKICAgICAgdGEuc3R5bGUudG9wID0gIi0xMDAwcHgiOwogICAgICB0YS5zdHlsZS5sZWZ0ID0gIjAiOwogICAgICB0YS5zdHlsZS5vcGFjaXR5ID0gIjAiOwogICAgICAoZG9jdW1lbnQuYm9keSB8fCBkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQpLmFwcGVuZENoaWxkKHRhKTsKICAgICAgdGEuZm9jdXMoKTsKICAgICAgdGEuc2VsZWN0KCk7CiAgICAgIHZhciBvayA9IGZhbHNlOwogICAgICB0cnkgeyBvayA9IGRvY3VtZW50LmV4ZWNDb21tYW5kKCJjb3B5Iik7IH0gY2F0Y2ggKF8pIHsgb2sgPSBmYWxzZTsgfQogICAgICBpZiAodGEucGFyZW50Tm9kZSkgdGEucGFyZW50Tm9kZS5yZW1vdmVDaGlsZCh0YSk7CiAgICAgIGlmIChzYXZlZCAmJiBzZWwpIHsgdHJ5IHsgc2VsLnJlbW92ZUFsbFJhbmdlcygpOyBzZWwuYWRkUmFuZ2Uoc2F2ZWQpOyB9IGNhdGNoIChfKSB7fSB9CiAgICAgIGlmIChwcmV2ICYmIHByZXYuZm9jdXMpIHsgdHJ5IHsgcHJldi5mb2N1cygpOyB9IGNhdGNoIChfKSB7fSB9CiAgICAgIHJldHVybiAhIW9rOwogICAgfSBjYXRjaCAoXykgeyByZXR1cm4gZmFsc2U7IH0KICB9CgogIC8vIENvcHkgYHRleHRgIGFuZCByZXNvbHZlIHRvIHdoZXRoZXIgdGhlIGNvcHkgQUNUVUFMTFkgaGFwcGVuZWQsIHNvIGNhbGxlcnMgb25seQogIC8vIHNob3cgc3VjY2VzcyBvbiBhIHJlYWwgY29weSAtLSBuZXZlciBhIGZhbHNlICJjb3BpZWQiICh0aGUgb3JpZ2luYWwgYnVnOgogIC8vIG5hdmlnYXRvci5jbGlwYm9hcmQgd2FzIHVuZGVmaW5lZCBpbiB0aGUgd2VidmlldywgdGhlIGNvZGUgZmVsbCB0aHJvdWdoIHRvCiAgLy8gUHJvbWlzZS5yZXNvbHZlKCksIGFuZCB0aGUgVUkgY2xhaW1lZCBzdWNjZXNzIHdoaWxlIG5vdGhpbmcgd2FzIHdyaXR0ZW4pLiBFbXB0eQogIC8vIHRleHQgaXMgYSBub24tY29weSAtPiBmYWxzZS4gZXhlY0NvbW1hbmQgZmlyc3QgKGdlc3R1cmUtc2FmZSwgc2VjdXJlLWNvbnRleHQtCiAgLy8gaW5kZXBlbmRlbnQpOyB0aGUgYXN5bmMgQ2xpcGJvYXJkIEFQSSBpcyB0aGUgZmFsbGJhY2suIE5ldmVyIHRocm93cy4KICBmdW5jdGlvbiBjb3B5VGV4dCh0ZXh0KSB7CiAgICB2YXIgcyA9ICh0ZXh0ID09IG51bGwpID8gIiIgOiBTdHJpbmcodGV4dCk7CiAgICBpZiAoIXMpIHJldHVybiBQcm9taXNlLnJlc29sdmUoZmFsc2UpOwogICAgaWYgKGV4ZWNDb3B5KHMpKSByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKHRydWUpOwogICAgdHJ5IHsKICAgICAgaWYgKHR5cGVvZiBuYXZpZ2F0b3IgIT09ICJ1bmRlZmluZWQiICYmIG5hdmlnYXRvci5jbGlwYm9hcmQgJiYgbmF2aWdhdG9yLmNsaXBib2FyZC53cml0ZVRleHQpIHsKICAgICAgICByZXR1cm4gbmF2aWdhdG9yLmNsaXBib2FyZC53cml0ZVRleHQocykudGhlbigKICAgICAgICAgIGZ1bmN0aW9uICgpIHsgcmV0dXJuIHRydWU7IH0sCiAgICAgICAgICBmdW5jdGlvbiAoKSB7IHJldHVybiBmYWxzZTsgfQogICAgICAgICk7CiAgICAgIH0KICAgIH0gY2F0Y2ggKF8pIHt9CiAgICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKGZhbHNlKTsKICB9CgogIGZ1bmN0aW9uIGJ1YmJsZU1hcmtkb3duKGJ1YmJsZSwgcm9sZSkgewogICAgdmFyIGNsZWFuID0gc2FuaXRpemVDbG9uZShjb250ZW50Tm9kZU9mKGJ1YmJsZSwgcm9sZSkpOwogICAgcmV0dXJuIHJvbGUgPT09ICJhc3Npc3RhbnQiID8gaHRtbFRvTWFya2Rvd24oY2xlYW4pIDogKGNsZWFuLnRleHRDb250ZW50IHx8ICIiKS50cmltKCk7CiAgfQoKICAvLyBJbmxpbmUgU1ZHIGljb25zIChjdXJyZW50Q29sb3IsIH4xNHB4KS4gU2V0IHZpYSBpbm5lckhUTUwgb24gb3VyIG93biBidXR0b25zCiAgLy8gb25seTsgdGhlIG1hcmt1cCBuZXZlciByZWFjaGVzIGNvcGllZCBjb250ZW50IChzYW5pdGl6ZUNsb25lIGRyb3BzIG91ciBub2RlcykuCiAgdmFyIElDT05fQ09QWSA9ICc8c3ZnIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGFyaWEtaGlkZGVuPSJ0cnVlIj48cmVjdCB4PSI5IiB5PSI5IiB3aWR0aD0iMTMiIGhlaWdodD0iMTMiIHJ4PSIyIiByeT0iMiI+PC9yZWN0PjxwYXRoIGQ9Ik01IDE1SDRhMiAyIDAgMCAxLTItMlY0YTIgMiAwIDAgMSAyLTJoOWEyIDIgMCAwIDEgMiAydjEiPjwvcGF0aD48L3N2Zz4nOwogIHZhciBJQ09OX0NIRUNLID0gJzxzdmcgd2lkdGg9IjE0IiBoZWlnaHQ9IjE0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjIuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBhcmlhLWhpZGRlbj0idHJ1ZSI+PHBvbHlsaW5lIHBvaW50cz0iMjAgNiA5IDE3IDQgMTIiPjwvcG9seWxpbmU+PC9zdmc+JzsKCiAgLy8gRmxpcCB0aGUgYnV0dG9uIHRvIGEgY2hlY2ttYXJrIGZvciBGRUVEQkFDS19NUywgdGhlbiByZXN0b3JlLiBJZGVtcG90ZW50IGFjcm9zcwogIC8vIHJhcGlkIGNsaWNrcyAoYW55IHBlbmRpbmcgcmVzdG9yZSBpcyBjbGVhcmVkIGZpcnN0KS4KICBmdW5jdGlvbiBzaG93Q29waWVkKGJ0bikgewogICAgdHJ5IHsKICAgICAgaWYgKGJ0bi5fX2NjVGltZXIpIGNsZWFyVGltZW91dChidG4uX19jY1RpbWVyKTsKICAgICAgYnRuLmNsYXNzTGlzdC5hZGQoQ09OVFJPTF9QUkVGSVggKyAiLW9rIik7CiAgICAgIGJ0bi5pbm5lckhUTUwgPSBJQ09OX0NIRUNLOwogICAgICBidG4uX19jY1RpbWVyID0gc2V0VGltZW91dChmdW5jdGlvbiAoKSB7CiAgICAgICAgdHJ5IHsgYnRuLmNsYXNzTGlzdC5yZW1vdmUoQ09OVFJPTF9QUkVGSVggKyAiLW9rIik7IGJ0bi5pbm5lckhUTUwgPSBJQ09OX0NPUFk7IH0gY2F0Y2ggKF8pIHt9CiAgICAgICAgYnRuLl9fY2NUaW1lciA9IG51bGw7CiAgICAgIH0sIEZFRURCQUNLX01TKTsKICAgIH0gY2F0Y2ggKF8pIHt9CiAgfQoKICAvLyBCdWlsZCBhIHNpbmdsZSBjb250cm9sOiBvbmUgY2xpcGJvYXJkLWljb24gYnV0dG9uLiBgb25Db3B5KClgIGlzIGludm9rZWQKICAvLyBzeW5jaHJvbm91c2x5IG9uIGNsaWNrIChzbyB0aGUgY29weSBzdGF5cyBpbnNpZGUgdGhlIHVzZXIgZ2VzdHVyZSkgYW5kIG11c3QKICAvLyByZXR1cm4gYSBQcm9taXNlPGJvb2xlYW4+OyB0aGUgY2hlY2ttYXJrIHNob3dzIG9ubHkgd2hlbiBpdCByZXNvbHZlcyB0cnVlLiBBbGwKICAvLyBub2RlcyBjYXJyeSB0aGUgQ09OVFJPTF9QUkVGSVggY2xhc3Mgc28gc2FuaXRpemVDbG9uZSBzdHJpcHMgdGhlbSBmcm9tIGNvcGllcy4KICBmdW5jdGlvbiBidWlsZENvbnRyb2wob25Db3B5LCB0aXRsZSkgewogICAgdmFyIHdyYXAgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzcGFuIik7CiAgICB3cmFwLmNsYXNzTmFtZSA9IENPTlRST0xfUFJFRklYOwogICAgdmFyIGJ0biA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImJ1dHRvbiIpOwogICAgYnRuLnR5cGUgPSAiYnV0dG9uIjsKICAgIGJ0bi5jbGFzc05hbWUgPSBDT05UUk9MX1BSRUZJWCArICItYnRuIjsKICAgIGJ0bi50aXRsZSA9IHRpdGxlIHx8ICJDb3B5IGFzIE1hcmtkb3duIjsKICAgIGJ0bi5zZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWwiLCBidG4udGl0bGUpOwogICAgYnRuLmlubmVySFRNTCA9IElDT05fQ09QWTsKICAgIHZhciBidXN5ID0gZmFsc2U7CiAgICBidG4uYWRkRXZlbnRMaXN0ZW5lcigiY2xpY2siLCBmdW5jdGlvbiAoZSkgewogICAgICBlLnN0b3BQcm9wYWdhdGlvbigpOwogICAgICBpZiAoYnVzeSkgcmV0dXJuOwogICAgICBidXN5ID0gdHJ1ZTsKICAgICAgdmFyIHA7CiAgICAgIHRyeSB7IHAgPSBvbkNvcHkoKTsgfSBjYXRjaCAoXykgeyBwID0gZmFsc2U7IH0KICAgICAgUHJvbWlzZS5yZXNvbHZlKHApLnRoZW4oCiAgICAgICAgZnVuY3Rpb24gKG9rKSB7IGJ1c3kgPSBmYWxzZTsgaWYgKG9rKSBzaG93Q29waWVkKGJ0bik7IH0sCiAgICAgICAgZnVuY3Rpb24gKCkgeyBidXN5ID0gZmFsc2U7IH0KICAgICAgKTsKICAgIH0pOwogICAgd3JhcC5hcHBlbmRDaGlsZChidG4pOwogICAgcmV0dXJuIHdyYXA7CiAgfQoKICBmdW5jdGlvbiBkZWNvcmF0ZShidWJibGUpIHsKICAgIHRyeSB7CiAgICAgIHZhciByb2xlID0gY2xhc3NpZnlCdWJibGUoYnViYmxlKTsKICAgICAgaWYgKCFyb2xlKSByZXR1cm47CiAgICAgIC8vIElkZW1wb3RlbnQ6IGtlZXAgZXhhY3RseSBvbmUgY29udHJvbC4gQSBSZWFjdCByZS1yZW5kZXIgb2YgdGhlIGJ1YmJsZSBjYW4KICAgICAgLy8gbGVhdmUgYSBzdGFsZSBjb250cm9sIGJlaGluZCBvciB0cmFuc2llbnRseSBkZWZlYXQgYW4gImFscmVhZHkgZGVjb3JhdGVkIgogICAgICAvLyBndWFyZCwgd2hpY2ggaXMgd2hhdCBwcm9kdWNlZCBkdXBsaWNhdGUgcm93cyBvZiBidXR0b25zOyBwcnVuZSBhbnkgZXh0cmFzCiAgICAgIC8vIGV2ZXJ5IHN3ZWVwIGFuZCBvbmx5IGFkZCBvbmUgd2hlbiBub25lIHJlbWFpbi4KICAgICAgdmFyIGV4aXN0aW5nID0gYnViYmxlLnF1ZXJ5U2VsZWN0b3JBbGwgPyBidWJibGUucXVlcnlTZWxlY3RvckFsbCgiLiIgKyBDT05UUk9MX1BSRUZJWCkgOiBudWxsOwogICAgICBpZiAoZXhpc3RpbmcgJiYgZXhpc3RpbmcubGVuZ3RoKSB7CiAgICAgICAgZm9yICh2YXIgaSA9IGV4aXN0aW5nLmxlbmd0aCAtIDE7IGkgPj0gMTsgaS0tKSB7CiAgICAgICAgICBpZiAoZXhpc3RpbmdbaV0gJiYgZXhpc3RpbmdbaV0ucGFyZW50Tm9kZSkgZXhpc3RpbmdbaV0ucGFyZW50Tm9kZS5yZW1vdmVDaGlsZChleGlzdGluZ1tpXSk7CiAgICAgICAgfQogICAgICAgIHJldHVybjsKICAgICAgfQogICAgICB2YXIgY29udHJvbCA9IGJ1aWxkQ29udHJvbChmdW5jdGlvbiAoKSB7CiAgICAgICAgcmV0dXJuIGNvcHlUZXh0KGJ1YmJsZU1hcmtkb3duKGJ1YmJsZSwgcm9sZSkpOwogICAgICB9LCAiQ29weSBhcyBNYXJrZG93biIpOwogICAgICBidWJibGUuYXBwZW5kQ2hpbGQoY29udHJvbCk7CiAgICB9IGNhdGNoIChfKSB7fQogIH0KCiAgZnVuY3Rpb24gY29weUNvbnZlcnNhdGlvbigpIHsKICAgIHZhciBidWJibGVzID0gcXNhKFVTRVJfQlVCQkxFICsgIiwiICsgQVNTSVNUQU5UX0JVQkJMRSk7CiAgICByZXR1cm4gY29weVRleHQoY29udmVyc2F0aW9uVG9NYXJrZG93bihidWJibGVzLCBmdW5jdGlvbiAoYikgewogICAgICByZXR1cm4gY29udGVudE5vZGVPZihiLCBjbGFzc2lmeUJ1YmJsZShiKSk7CiAgICB9KSk7CiAgfQoKICAvLyBBIHNpbmdsZSBmbG9hdGluZyAiQ29weSBjb252ZXJzYXRpb24iIGljb24sIHByZXNlbnQgb25seSB3aGlsZSBhIGNvbnZlcnNhdGlvbgogIC8vIGlzIG9wZW4gKHNvIGl0IG5ldmVyIGNsdXR0ZXJzIHRoZSBoaXN0b3J5LWxpc3QgdmlldykuIFBpbm5lZCB0b3AtcmlnaHQgYnkgQ1NTLAogIC8vIGNsZWFyIG9mIHRoZSBjaGF0IGlucHV0IGF0IHRoZSBib3R0b207IHRoZSBtb3N0LXJlY2VudC1wcm9tcHQgc3RpY2t5IGhlYWRlcgogIC8vIHNpdHMgdG8gaXRzIGxlZnQuCiAgZnVuY3Rpb24gaW5zdGFsbENvbnZlcnNhdGlvbkNvbnRyb2woKSB7CiAgICB0cnkgewogICAgICB2YXIgZXhpc3RpbmcgPSBxcyhkb2N1bWVudCwgIi4iICsgQ09OVFJPTF9QUkVGSVggKyAiLWNvbnZlcnNhdGlvbiIpOwogICAgICB2YXIgaGFzTWVzc2FnZXMgPSBxc2EoVVNFUl9CVUJCTEUgKyAiLCIgKyBBU1NJU1RBTlRfQlVCQkxFKS5sZW5ndGggPiAwOwogICAgICBpZiAoIWhhc01lc3NhZ2VzKSB7CiAgICAgICAgaWYgKGV4aXN0aW5nICYmIGV4aXN0aW5nLnBhcmVudE5vZGUpIGV4aXN0aW5nLnBhcmVudE5vZGUucmVtb3ZlQ2hpbGQoZXhpc3RpbmcpOwogICAgICAgIHJldHVybjsKICAgICAgfQogICAgICBpZiAoZXhpc3RpbmcpIHJldHVybjsKICAgICAgdmFyIGJhciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpOwogICAgICBiYXIuY2xhc3NOYW1lID0gQ09OVFJPTF9QUkVGSVggKyAiLWNvbnZlcnNhdGlvbiI7CiAgICAgIGJhci5hcHBlbmRDaGlsZChidWlsZENvbnRyb2woY29weUNvbnZlcnNhdGlvbiwgIkNvcHkgY29udmVyc2F0aW9uIikpOwogICAgICBkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKGJhcik7CiAgICB9IGNhdGNoIChfKSB7fQogIH0KCiAgZnVuY3Rpb24gc3dlZXAoKSB7CiAgICB2YXIgYiA9IHFzYShVU0VSX0JVQkJMRSArICIsIiArIEFTU0lTVEFOVF9CVUJCTEUpOwogICAgZm9yICh2YXIgaSA9IDA7IGkgPCBiLmxlbmd0aDsgaSsrKSBkZWNvcmF0ZShiW2ldKTsKICAgIGluc3RhbGxDb252ZXJzYXRpb25Db250cm9sKCk7CiAgfQoKICBmdW5jdGlvbiBib290KCkgewogICAgdHJ5IHsKICAgICAgdmFyIHRhcmdldCA9IChNRVNTQUdFU19DT05UQUlORVIgJiYgcXMoZG9jdW1lbnQsIE1FU1NBR0VTX0NPTlRBSU5FUikpIHx8IGRvY3VtZW50LmJvZHk7CiAgICAgIHN3ZWVwKCk7CiAgICAgIGlmICh0eXBlb2YgTXV0YXRpb25PYnNlcnZlciA9PT0gInVuZGVmaW5lZCIpIHJldHVybjsKICAgICAgdmFyIG9icyA9IG5ldyBNdXRhdGlvbk9ic2VydmVyKGZ1bmN0aW9uICgpIHsgc3dlZXAoKTsgfSk7CiAgICAgIG9icy5vYnNlcnZlKHRhcmdldCwgeyBjaGlsZExpc3Q6IHRydWUsIHN1YnRyZWU6IHRydWUgfSk7CiAgICB9IGNhdGNoIChfKSB7fQogIH0KfSkoKTsK").decode("utf-8") +INJECT_CSS = base64.b64decode("LmNjLW1kLWNvcHkgewogIGRpc3BsYXk6IGlubGluZS1mbGV4OwogIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgdmVydGljYWwtYWxpZ246IG1pZGRsZTsKICBtYXJnaW4tbGVmdDogNnB4Owp9Ci5jYy1tZC1jb3B5LWJ0biB7CiAgZGlzcGxheTogaW5saW5lLWZsZXg7CiAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjsKICBwYWRkaW5nOiAycHg7CiAgY29sb3I6IHZhcigtLXZzY29kZS1mb3JlZ3JvdW5kKTsKICBiYWNrZ3JvdW5kOiB0cmFuc3BhcmVudDsKICBib3JkZXI6IG5vbmU7CiAgYm9yZGVyLXJhZGl1czogNHB4OwogIGN1cnNvcjogcG9pbnRlcjsKICBvcGFjaXR5OiAwLjY7Cn0KLmNjLW1kLWNvcHktYnRuIHN2ZyB7CiAgZGlzcGxheTogYmxvY2s7CiAgd2lkdGg6IDE0cHg7CiAgaGVpZ2h0OiAxNHB4Owp9Ci5jYy1tZC1jb3B5LWJ0bjpob3ZlciB7CiAgb3BhY2l0eTogMTsKICBiYWNrZ3JvdW5kOiB2YXIoLS12c2NvZGUtdG9vbGJhci1ob3ZlckJhY2tncm91bmQsIHJnYmEoMTI4LCAxMjgsIDEyOCwgMC4xNSkpOwp9Ci8qIFN1Y2Nlc3Mgc3RhdGU6IHRoZSBpY29uIGlzIGEgZ3JlZW4gY2hlY2ttYXJrIGZvciBhIG1vbWVudCBhZnRlciBhIHJlYWwgY29weS4gKi8KLmNjLW1kLWNvcHktYnRuLmNjLW1kLWNvcHktb2ssCi5jYy1tZC1jb3B5LWJ0bi5jYy1tZC1jb3B5LW9rOmhvdmVyIHsKICBvcGFjaXR5OiAxOwogIGNvbG9yOiB2YXIoLS12c2NvZGUtY2hhcnRzLWdyZWVuLCB2YXIoLS12c2NvZGUtdGVzdGluZy1pY29uUGFzc2VkLCAjODlkMTg1KSk7CiAgYmFja2dyb3VuZDogdHJhbnNwYXJlbnQ7Cn0KLyogV2hvbGUtY29udmVyc2F0aW9uIGNvcHk6IGEgc2luZ2xlIGZsb2F0aW5nIGljb24gcGlubmVkIHRvIHRoZSB0b3AtcmlnaHQgY29ybmVyLAogICBjbGVhciBvZiB0aGUgY2hhdCBpbnB1dCBhdCB0aGUgYm90dG9tLiBTaG93biBvbmx5IHdoaWxlIGEgY29udmVyc2F0aW9uIGlzIG9wZW4KICAgKHRoZSBJSUZFIGFkZHMvcmVtb3ZlcyBpdCkuIE51ZGdlIHRvcC9yaWdodCBoZXJlIGlmIGl0IGNyb3dkcyB0aGUgc3RpY2t5IGhlYWRlci4gKi8KLmNjLW1kLWNvcHktY29udmVyc2F0aW9uIHsKICBwb3NpdGlvbjogZml4ZWQ7CiAgdG9wOiAyNnB4OwogIHJpZ2h0OiA0cHg7CiAgei1pbmRleDogMzA7CiAgZGlzcGxheTogaW5saW5lLWZsZXg7CiAgcGFkZGluZzogMnB4OwogIGJhY2tncm91bmQ6IHZhcigtLXZzY29kZS1lZGl0b3JXaWRnZXQtYmFja2dyb3VuZCk7CiAgYm9yZGVyOiAxcHggc29saWQgdmFyKC0tdnNjb2RlLXdpZGdldC1ib3JkZXIsIHRyYW5zcGFyZW50KTsKICBib3JkZXItcmFkaXVzOiA2cHg7CiAgb3BhY2l0eTogMC44NTsKfQouY2MtbWQtY29weS1jb252ZXJzYXRpb24gLmNjLW1kLWNvcHkgewogIG1hcmdpbi1sZWZ0OiAwOwp9Ci5jYy1tZC1jb3B5LWNvbnZlcnNhdGlvbjpob3ZlciB7CiAgb3BhY2l0eTogMTsKfQo=").decode("utf-8") # << stdout (markdown) python3 cc-export.py --session ID # a specific session id python3 cc-export.py --format text # plain text python3 cc-export.py --include-thinking --include-tools python3 cc-export.py -o out.md # write to a file - python3 cc-export.py --open # open the raw transcript in VS Code python3 cc-export.py --cwd /path/to/proj # resolve as if launched from there The JSONL is an internal format that can change, so each row's shape is validated @@ -21,7 +20,6 @@ import glob import json import os -import subprocess import sys import unicodedata @@ -157,7 +155,6 @@ def main(argv=None, config=None, env=None): ap.add_argument("--format", choices=("markdown", "text"), default="markdown") ap.add_argument("--include-thinking", action="store_true") ap.add_argument("--include-tools", action="store_true") - ap.add_argument("--open", action="store_true", help="open the raw .jsonl in VS Code") ap.add_argument("-o", "--output", default=None, help="write to FILE (default: stdout)") args = ap.parse_args(argv) @@ -168,15 +165,6 @@ def main(argv=None, config=None, env=None): sys.stderr.write("cc-export: no matching session transcript found\n") return 1 - if args.open: - editor = env.get("CC_EDITOR", "code") - try: - subprocess.run([editor, path], check=False, env=env) - except FileNotFoundError: - sys.stderr.write(f"cc-export: editor '{editor}' not found on PATH\n") - return 1 - return 0 - out = render( read_rows(path), fmt=args.format, diff --git a/fixes/markdown-copy-export/webview-inject.css b/fixes/markdown-copy-export/webview-inject.css index 21ce251..321e5a2 100644 --- a/fixes/markdown-copy-export/webview-inject.css +++ b/fixes/markdown-copy-export/webview-inject.css @@ -1,54 +1,55 @@ .cc-md-copy { display: inline-flex; align-items: center; - gap: 2px; vertical-align: middle; margin-left: 6px; } -.cc-md-copy-btn, -.cc-md-copy-caret { - font: inherit; - font-size: 11px; - line-height: 1.4; - padding: 1px 6px; +.cc-md-copy-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 2px; color: var(--vscode-foreground); background: transparent; - border: 1px solid var(--vscode-widget-border, transparent); + border: none; border-radius: 4px; cursor: pointer; - opacity: 0.65; + opacity: 0.6; } -.cc-md-copy-btn:hover, -.cc-md-copy-caret:hover { +.cc-md-copy-btn svg { + display: block; + width: 14px; + height: 14px; +} +.cc-md-copy-btn:hover { opacity: 1; background: var(--vscode-toolbar-hoverBackground, rgba(128, 128, 128, 0.15)); } -.cc-md-copy-menu { - position: relative; - margin-left: 4px; - padding: 2px; - background: var(--vscode-menu-background, var(--vscode-editorWidget-background)); - border: 1px solid var(--vscode-menu-border, var(--vscode-widget-border, transparent)); - border-radius: 4px; - z-index: 5; -} -.cc-md-copy-feedback { - margin-left: 6px; - font-size: 11px; - opacity: 0.85; - color: var(--vscode-foreground); +/* Success state: the icon is a green checkmark for a moment after a real copy. */ +.cc-md-copy-btn.cc-md-copy-ok, +.cc-md-copy-btn.cc-md-copy-ok:hover { + opacity: 1; + color: var(--vscode-charts-green, var(--vscode-testing-iconPassed, #89d185)); + background: transparent; } +/* Whole-conversation copy: a single floating icon pinned to the top-right corner, + clear of the chat input at the bottom. Shown only while a conversation is open + (the IIFE adds/removes it). Nudge top/right here if it crowds the sticky header. */ .cc-md-copy-conversation { position: fixed; - right: 16px; - bottom: 56px; - z-index: 10; + top: 26px; + right: 4px; + z-index: 30; + display: inline-flex; padding: 2px; background: var(--vscode-editorWidget-background); border: 1px solid var(--vscode-widget-border, transparent); border-radius: 6px; opacity: 0.85; } +.cc-md-copy-conversation .cc-md-copy { + margin-left: 0; +} .cc-md-copy-conversation:hover { opacity: 1; } diff --git a/fixes/markdown-copy-export/webview-inject.js b/fixes/markdown-copy-export/webview-inject.js index 28f802a..16a3501 100644 --- a/fixes/markdown-copy-export/webview-inject.js +++ b/fixes/markdown-copy-export/webview-inject.js @@ -1,7 +1,9 @@ -/* cc-md-copy: per-message and whole-conversation copy (markdown/plain) for the +/* cc-md-copy: per-message and whole-conversation copy (Markdown) for the * Claude Code VS Code webview. Self-contained IIFE appended to webview/index.js. - * Additive and read-only w.r.t. app state; keyed on stable CSS-module class - * prefixes, so it fails safe (controls simply do not appear) if a prefix moves. + * Each control is a single clipboard icon that flips to a checkmark for ~2s when a + * copy actually succeeds (no text label, no menu). Additive and read-only w.r.t. + * app state; keyed on stable CSS-module class prefixes, so it fails safe (controls + * simply do not appear) if a prefix moves. * Exposes its pure functions for node unit tests; boot()s only in a real webview. */ /* Leading ';' so that, appended after the bundle, this IIFE can never be parsed as * a call on the bundle's final expression if it lacks a trailing semicolon (ASI @@ -22,7 +24,7 @@ // not a per-block class (a turn has multiple blocks). "" -> use the bubble itself // (already aggregates all blocks; sanitizeClone is the correctness gate). var ASSISTANT_CONTENT = ""; - var FEEDBACK_MS = 1800; + var FEEDBACK_MS = 2000; // how long the checkmark shows after a successful copy // ---- HTML -> Markdown (DOM walk) ------------------------------------------- // Uses only: nodeType, tagName, childNodes, textContent, getAttribute, className. @@ -241,7 +243,8 @@ boot(); } else if (typeof module !== "undefined" && module.exports) { module.exports = { htmlToMarkdown: htmlToMarkdown, sanitizeClone: sanitizeClone, - classifyBubble: classifyBubble, conversationToMarkdown: conversationToMarkdown }; + classifyBubble: classifyBubble, conversationToMarkdown: conversationToMarkdown, + copyText: copyText }; } // ---- live-webview wiring (runs only when a document exists) ---------------- @@ -262,64 +265,107 @@ return bubble; } - function copyText(text) { + // Copy `s` via a synchronous execCommand("copy") on an off-screen textarea, and + // report whether it actually happened. Done first (and synchronously) because it + // runs inside the click gesture and works whether or not the page is a secure + // context -- so it covers remote / code-server, where the async Clipboard API is + // simply absent. Restores the prior selection/focus so it is invisible. + function execCopy(s) { try { - if (navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(text); - } catch (_) {} - return Promise.resolve(); // best-effort; never throw into the app + if (typeof document === "undefined" || !document.createElement) return false; + var prev = document.activeElement || null; + var sel = document.getSelection ? document.getSelection() : null; + var saved = (sel && sel.rangeCount) ? sel.getRangeAt(0) : null; + var ta = document.createElement("textarea"); + ta.value = s; + ta.setAttribute("readonly", ""); + ta.style.position = "fixed"; + ta.style.top = "-1000px"; + ta.style.left = "0"; + ta.style.opacity = "0"; + (document.body || document.documentElement).appendChild(ta); + ta.focus(); + ta.select(); + var ok = false; + try { ok = document.execCommand("copy"); } catch (_) { ok = false; } + if (ta.parentNode) ta.parentNode.removeChild(ta); + if (saved && sel) { try { sel.removeAllRanges(); sel.addRange(saved); } catch (_) {} } + if (prev && prev.focus) { try { prev.focus(); } catch (_) {} } + return !!ok; + } catch (_) { return false; } } - function flashFeedback(host) { + // Copy `text` and resolve to whether the copy ACTUALLY happened, so callers only + // show success on a real copy -- never a false "copied" (the original bug: + // navigator.clipboard was undefined in the webview, the code fell through to + // Promise.resolve(), and the UI claimed success while nothing was written). Empty + // text is a non-copy -> false. execCommand first (gesture-safe, secure-context- + // independent); the async Clipboard API is the fallback. Never throws. + function copyText(text) { + var s = (text == null) ? "" : String(text); + if (!s) return Promise.resolve(false); + if (execCopy(s)) return Promise.resolve(true); try { - var fb = document.createElement("span"); - fb.className = CONTROL_PREFIX + "-feedback"; - fb.textContent = "Copied"; - host.appendChild(fb); - setTimeout(function () { if (fb && fb.parentNode) fb.parentNode.removeChild(fb); }, FEEDBACK_MS); + if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.writeText) { + return navigator.clipboard.writeText(s).then( + function () { return true; }, + function () { return false; } + ); + } } catch (_) {} + return Promise.resolve(false); } function bubbleMarkdown(bubble, role) { var clean = sanitizeClone(contentNodeOf(bubble, role)); return role === "assistant" ? htmlToMarkdown(clean) : (clean.textContent || "").trim(); } - function bubblePlain(bubble, role) { - return (sanitizeClone(contentNodeOf(bubble, role)).textContent || "").trim(); + + // Inline SVG icons (currentColor, ~14px). Set via innerHTML on our own buttons + // only; the markup never reaches copied content (sanitizeClone drops our nodes). + var ICON_COPY = ''; + var ICON_CHECK = ''; + + // Flip the button to a checkmark for FEEDBACK_MS, then restore. Idempotent across + // rapid clicks (any pending restore is cleared first). + function showCopied(btn) { + try { + if (btn.__ccTimer) clearTimeout(btn.__ccTimer); + btn.classList.add(CONTROL_PREFIX + "-ok"); + btn.innerHTML = ICON_CHECK; + btn.__ccTimer = setTimeout(function () { + try { btn.classList.remove(CONTROL_PREFIX + "-ok"); btn.innerHTML = ICON_COPY; } catch (_) {} + btn.__ccTimer = null; + }, FEEDBACK_MS); + } catch (_) {} } - // Build a single control: a primary "Copy" (markdown) plus a small caret that - // toggles a menu with "Copy as plain text". All nodes carry the CONTROL_PREFIX - // class so sanitizeClone removes them from any copied content. - function buildControl(onMarkdown, onPlain) { + // Build a single control: one clipboard-icon button. `onCopy()` is invoked + // synchronously on click (so the copy stays inside the user gesture) and must + // return a Promise; the checkmark shows only when it resolves true. All + // nodes carry the CONTROL_PREFIX class so sanitizeClone strips them from copies. + function buildControl(onCopy, title) { var wrap = document.createElement("span"); wrap.className = CONTROL_PREFIX; - var primary = document.createElement("button"); - primary.type = "button"; - primary.className = CONTROL_PREFIX + "-btn"; - primary.title = "Copy as Markdown"; - primary.textContent = "Copy"; - primary.addEventListener("click", function (e) { e.stopPropagation(); onMarkdown(primary); }); - var caret = document.createElement("button"); - caret.type = "button"; - caret.className = CONTROL_PREFIX + "-caret"; - caret.title = "Copy options"; - caret.textContent = "â–¾"; // black down-pointing small triangle - var menu = document.createElement("span"); - menu.className = CONTROL_PREFIX + "-menu"; - menu.style.display = "none"; - var plain = document.createElement("button"); - plain.type = "button"; - plain.className = CONTROL_PREFIX + "-btn"; - plain.textContent = "Copy as plain text"; - plain.addEventListener("click", function (e) { e.stopPropagation(); menu.style.display = "none"; onPlain(plain); }); - menu.appendChild(plain); - caret.addEventListener("click", function (e) { + var btn = document.createElement("button"); + btn.type = "button"; + btn.className = CONTROL_PREFIX + "-btn"; + btn.title = title || "Copy as Markdown"; + btn.setAttribute("aria-label", btn.title); + btn.innerHTML = ICON_COPY; + var busy = false; + btn.addEventListener("click", function (e) { e.stopPropagation(); - menu.style.display = menu.style.display === "none" ? "inline-block" : "none"; + if (busy) return; + busy = true; + var p; + try { p = onCopy(); } catch (_) { p = false; } + Promise.resolve(p).then( + function (ok) { busy = false; if (ok) showCopied(btn); }, + function () { busy = false; } + ); }); - wrap.appendChild(primary); - wrap.appendChild(caret); - wrap.appendChild(menu); + wrap.appendChild(btn); return wrap; } @@ -327,54 +373,61 @@ try { var role = classifyBubble(bubble); if (!role) return; - if (qs(bubble, "." + CONTROL_PREFIX)) return; // already decorated - var control = buildControl( - function (host) { copyText(bubbleMarkdown(bubble, role)).then(function () { flashFeedback(control); }); }, - function (host) { copyText(bubblePlain(bubble, role)).then(function () { flashFeedback(control); }); } - ); + // Idempotent: keep exactly one control. A React re-render of the bubble can + // leave a stale control behind or transiently defeat an "already decorated" + // guard, which is what produced duplicate rows of buttons; prune any extras + // every sweep and only add one when none remain. + var existing = bubble.querySelectorAll ? bubble.querySelectorAll("." + CONTROL_PREFIX) : null; + if (existing && existing.length) { + for (var i = existing.length - 1; i >= 1; i--) { + if (existing[i] && existing[i].parentNode) existing[i].parentNode.removeChild(existing[i]); + } + return; + } + var control = buildControl(function () { + return copyText(bubbleMarkdown(bubble, role)); + }, "Copy as Markdown"); bubble.appendChild(control); } catch (_) {} } - function copyConversation(format) { + function copyConversation() { var bubbles = qsa(USER_BUBBLE + "," + ASSISTANT_BUBBLE); - if (format === "text") { - var lines = []; - for (var i = 0; i < bubbles.length; i++) { - var role = classifyBubble(bubbles[i]); - if (!role) continue; - var body = bubblePlain(bubbles[i], role); - if (body) lines.push(body); - } - return copyText(lines.join("\n\n") + (lines.length ? "\n" : "")); - } return copyText(conversationToMarkdown(bubbles, function (b) { return contentNodeOf(b, classifyBubble(b)); })); } + // A single floating "Copy conversation" icon, present only while a conversation + // is open (so it never clutters the history-list view). Pinned top-right by CSS, + // clear of the chat input at the bottom; the most-recent-prompt sticky header + // sits to its left. function installConversationControl() { try { - if (qs(document, "." + CONTROL_PREFIX + "-conversation")) return; + var existing = qs(document, "." + CONTROL_PREFIX + "-conversation"); + var hasMessages = qsa(USER_BUBBLE + "," + ASSISTANT_BUBBLE).length > 0; + if (!hasMessages) { + if (existing && existing.parentNode) existing.parentNode.removeChild(existing); + return; + } + if (existing) return; var bar = document.createElement("div"); bar.className = CONTROL_PREFIX + "-conversation"; - var control = buildControl( - function () { copyConversation("markdown").then(function () { flashFeedback(bar); }); }, - function () { copyConversation("text").then(function () { flashFeedback(bar); }); } - ); - control.title = "Copy entire conversation"; - bar.appendChild(control); - document.body.appendChild(bar); // fixed-position via CSS; placement refined in Task 6 + bar.appendChild(buildControl(copyConversation, "Copy conversation")); + document.body.appendChild(bar); } catch (_) {} } - function sweep() { var b = qsa(USER_BUBBLE + "," + ASSISTANT_BUBBLE); for (var i = 0; i < b.length; i++) decorate(b[i]); } + function sweep() { + var b = qsa(USER_BUBBLE + "," + ASSISTANT_BUBBLE); + for (var i = 0; i < b.length; i++) decorate(b[i]); + installConversationControl(); + } function boot() { try { var target = (MESSAGES_CONTAINER && qs(document, MESSAGES_CONTAINER)) || document.body; sweep(); - installConversationControl(); if (typeof MutationObserver === "undefined") return; var obs = new MutationObserver(function () { sweep(); }); obs.observe(target, { childList: true, subtree: true }); diff --git a/launcher/claudemax b/launcher/claudemax index 8aa051a..d82c319 100755 --- a/launcher/claudemax +++ b/launcher/claudemax @@ -12,8 +12,9 @@ # fix #1) this wrapper idempotently patches the extension's webview bundle on # each launch, flipping the threshold so the icon shows at any usage level. # Because it re-applies every launch, it survives extension updates. -# 3. Adds per-message and whole-conversation "Copy" controls (Markdown / plain -# text) to the VS Code chat. Like fix #2 there is no env/CLI lever, so this +# 3. Adds a single-click "Copy as Markdown" icon to every message (and a floating +# "copy conversation" icon) in the VS Code chat; the icon flips to a checkmark +# only when the copy truly lands. Like fix #2 there is no env/CLI lever, so this # wrapper idempotently appends a self-contained block to the webview bundle # (index.js + index.css) each launch; it fails safe (the controls simply do # not appear if the markup moves) and survives extension updates. @@ -304,10 +305,12 @@ _cc_undo_context_icon() { # edit it by hand (CI drift check: tools/gen-embeds --check). # >>>CCWA-MD-COPY-EMBED>>> (generated by tools/gen-embeds; do not edit) _cc_md_copy_js() { cat <<'CCMDCOPYJS' -/* cc-md-copy: per-message and whole-conversation copy (markdown/plain) for the +/* cc-md-copy: per-message and whole-conversation copy (Markdown) for the * Claude Code VS Code webview. Self-contained IIFE appended to webview/index.js. - * Additive and read-only w.r.t. app state; keyed on stable CSS-module class - * prefixes, so it fails safe (controls simply do not appear) if a prefix moves. + * Each control is a single clipboard icon that flips to a checkmark for ~2s when a + * copy actually succeeds (no text label, no menu). Additive and read-only w.r.t. + * app state; keyed on stable CSS-module class prefixes, so it fails safe (controls + * simply do not appear) if a prefix moves. * Exposes its pure functions for node unit tests; boot()s only in a real webview. */ /* Leading ';' so that, appended after the bundle, this IIFE can never be parsed as * a call on the bundle's final expression if it lacks a trailing semicolon (ASI @@ -328,7 +331,7 @@ _cc_md_copy_js() { cat <<'CCMDCOPYJS' // not a per-block class (a turn has multiple blocks). "" -> use the bubble itself // (already aggregates all blocks; sanitizeClone is the correctness gate). var ASSISTANT_CONTENT = ""; - var FEEDBACK_MS = 1800; + var FEEDBACK_MS = 2000; // how long the checkmark shows after a successful copy // ---- HTML -> Markdown (DOM walk) ------------------------------------------- // Uses only: nodeType, tagName, childNodes, textContent, getAttribute, className. @@ -547,7 +550,8 @@ _cc_md_copy_js() { cat <<'CCMDCOPYJS' boot(); } else if (typeof module !== "undefined" && module.exports) { module.exports = { htmlToMarkdown: htmlToMarkdown, sanitizeClone: sanitizeClone, - classifyBubble: classifyBubble, conversationToMarkdown: conversationToMarkdown }; + classifyBubble: classifyBubble, conversationToMarkdown: conversationToMarkdown, + copyText: copyText }; } // ---- live-webview wiring (runs only when a document exists) ---------------- @@ -568,64 +572,107 @@ _cc_md_copy_js() { cat <<'CCMDCOPYJS' return bubble; } - function copyText(text) { + // Copy `s` via a synchronous execCommand("copy") on an off-screen textarea, and + // report whether it actually happened. Done first (and synchronously) because it + // runs inside the click gesture and works whether or not the page is a secure + // context -- so it covers remote / code-server, where the async Clipboard API is + // simply absent. Restores the prior selection/focus so it is invisible. + function execCopy(s) { try { - if (navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(text); - } catch (_) {} - return Promise.resolve(); // best-effort; never throw into the app + if (typeof document === "undefined" || !document.createElement) return false; + var prev = document.activeElement || null; + var sel = document.getSelection ? document.getSelection() : null; + var saved = (sel && sel.rangeCount) ? sel.getRangeAt(0) : null; + var ta = document.createElement("textarea"); + ta.value = s; + ta.setAttribute("readonly", ""); + ta.style.position = "fixed"; + ta.style.top = "-1000px"; + ta.style.left = "0"; + ta.style.opacity = "0"; + (document.body || document.documentElement).appendChild(ta); + ta.focus(); + ta.select(); + var ok = false; + try { ok = document.execCommand("copy"); } catch (_) { ok = false; } + if (ta.parentNode) ta.parentNode.removeChild(ta); + if (saved && sel) { try { sel.removeAllRanges(); sel.addRange(saved); } catch (_) {} } + if (prev && prev.focus) { try { prev.focus(); } catch (_) {} } + return !!ok; + } catch (_) { return false; } } - function flashFeedback(host) { + // Copy `text` and resolve to whether the copy ACTUALLY happened, so callers only + // show success on a real copy -- never a false "copied" (the original bug: + // navigator.clipboard was undefined in the webview, the code fell through to + // Promise.resolve(), and the UI claimed success while nothing was written). Empty + // text is a non-copy -> false. execCommand first (gesture-safe, secure-context- + // independent); the async Clipboard API is the fallback. Never throws. + function copyText(text) { + var s = (text == null) ? "" : String(text); + if (!s) return Promise.resolve(false); + if (execCopy(s)) return Promise.resolve(true); try { - var fb = document.createElement("span"); - fb.className = CONTROL_PREFIX + "-feedback"; - fb.textContent = "Copied"; - host.appendChild(fb); - setTimeout(function () { if (fb && fb.parentNode) fb.parentNode.removeChild(fb); }, FEEDBACK_MS); + if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.writeText) { + return navigator.clipboard.writeText(s).then( + function () { return true; }, + function () { return false; } + ); + } } catch (_) {} + return Promise.resolve(false); } function bubbleMarkdown(bubble, role) { var clean = sanitizeClone(contentNodeOf(bubble, role)); return role === "assistant" ? htmlToMarkdown(clean) : (clean.textContent || "").trim(); } - function bubblePlain(bubble, role) { - return (sanitizeClone(contentNodeOf(bubble, role)).textContent || "").trim(); + + // Inline SVG icons (currentColor, ~14px). Set via innerHTML on our own buttons + // only; the markup never reaches copied content (sanitizeClone drops our nodes). + var ICON_COPY = ''; + var ICON_CHECK = ''; + + // Flip the button to a checkmark for FEEDBACK_MS, then restore. Idempotent across + // rapid clicks (any pending restore is cleared first). + function showCopied(btn) { + try { + if (btn.__ccTimer) clearTimeout(btn.__ccTimer); + btn.classList.add(CONTROL_PREFIX + "-ok"); + btn.innerHTML = ICON_CHECK; + btn.__ccTimer = setTimeout(function () { + try { btn.classList.remove(CONTROL_PREFIX + "-ok"); btn.innerHTML = ICON_COPY; } catch (_) {} + btn.__ccTimer = null; + }, FEEDBACK_MS); + } catch (_) {} } - // Build a single control: a primary "Copy" (markdown) plus a small caret that - // toggles a menu with "Copy as plain text". All nodes carry the CONTROL_PREFIX - // class so sanitizeClone removes them from any copied content. - function buildControl(onMarkdown, onPlain) { + // Build a single control: one clipboard-icon button. `onCopy()` is invoked + // synchronously on click (so the copy stays inside the user gesture) and must + // return a Promise; the checkmark shows only when it resolves true. All + // nodes carry the CONTROL_PREFIX class so sanitizeClone strips them from copies. + function buildControl(onCopy, title) { var wrap = document.createElement("span"); wrap.className = CONTROL_PREFIX; - var primary = document.createElement("button"); - primary.type = "button"; - primary.className = CONTROL_PREFIX + "-btn"; - primary.title = "Copy as Markdown"; - primary.textContent = "Copy"; - primary.addEventListener("click", function (e) { e.stopPropagation(); onMarkdown(primary); }); - var caret = document.createElement("button"); - caret.type = "button"; - caret.className = CONTROL_PREFIX + "-caret"; - caret.title = "Copy options"; - caret.textContent = "â–¾"; // black down-pointing small triangle - var menu = document.createElement("span"); - menu.className = CONTROL_PREFIX + "-menu"; - menu.style.display = "none"; - var plain = document.createElement("button"); - plain.type = "button"; - plain.className = CONTROL_PREFIX + "-btn"; - plain.textContent = "Copy as plain text"; - plain.addEventListener("click", function (e) { e.stopPropagation(); menu.style.display = "none"; onPlain(plain); }); - menu.appendChild(plain); - caret.addEventListener("click", function (e) { + var btn = document.createElement("button"); + btn.type = "button"; + btn.className = CONTROL_PREFIX + "-btn"; + btn.title = title || "Copy as Markdown"; + btn.setAttribute("aria-label", btn.title); + btn.innerHTML = ICON_COPY; + var busy = false; + btn.addEventListener("click", function (e) { e.stopPropagation(); - menu.style.display = menu.style.display === "none" ? "inline-block" : "none"; + if (busy) return; + busy = true; + var p; + try { p = onCopy(); } catch (_) { p = false; } + Promise.resolve(p).then( + function (ok) { busy = false; if (ok) showCopied(btn); }, + function () { busy = false; } + ); }); - wrap.appendChild(primary); - wrap.appendChild(caret); - wrap.appendChild(menu); + wrap.appendChild(btn); return wrap; } @@ -633,54 +680,61 @@ _cc_md_copy_js() { cat <<'CCMDCOPYJS' try { var role = classifyBubble(bubble); if (!role) return; - if (qs(bubble, "." + CONTROL_PREFIX)) return; // already decorated - var control = buildControl( - function (host) { copyText(bubbleMarkdown(bubble, role)).then(function () { flashFeedback(control); }); }, - function (host) { copyText(bubblePlain(bubble, role)).then(function () { flashFeedback(control); }); } - ); + // Idempotent: keep exactly one control. A React re-render of the bubble can + // leave a stale control behind or transiently defeat an "already decorated" + // guard, which is what produced duplicate rows of buttons; prune any extras + // every sweep and only add one when none remain. + var existing = bubble.querySelectorAll ? bubble.querySelectorAll("." + CONTROL_PREFIX) : null; + if (existing && existing.length) { + for (var i = existing.length - 1; i >= 1; i--) { + if (existing[i] && existing[i].parentNode) existing[i].parentNode.removeChild(existing[i]); + } + return; + } + var control = buildControl(function () { + return copyText(bubbleMarkdown(bubble, role)); + }, "Copy as Markdown"); bubble.appendChild(control); } catch (_) {} } - function copyConversation(format) { + function copyConversation() { var bubbles = qsa(USER_BUBBLE + "," + ASSISTANT_BUBBLE); - if (format === "text") { - var lines = []; - for (var i = 0; i < bubbles.length; i++) { - var role = classifyBubble(bubbles[i]); - if (!role) continue; - var body = bubblePlain(bubbles[i], role); - if (body) lines.push(body); - } - return copyText(lines.join("\n\n") + (lines.length ? "\n" : "")); - } return copyText(conversationToMarkdown(bubbles, function (b) { return contentNodeOf(b, classifyBubble(b)); })); } + // A single floating "Copy conversation" icon, present only while a conversation + // is open (so it never clutters the history-list view). Pinned top-right by CSS, + // clear of the chat input at the bottom; the most-recent-prompt sticky header + // sits to its left. function installConversationControl() { try { - if (qs(document, "." + CONTROL_PREFIX + "-conversation")) return; + var existing = qs(document, "." + CONTROL_PREFIX + "-conversation"); + var hasMessages = qsa(USER_BUBBLE + "," + ASSISTANT_BUBBLE).length > 0; + if (!hasMessages) { + if (existing && existing.parentNode) existing.parentNode.removeChild(existing); + return; + } + if (existing) return; var bar = document.createElement("div"); bar.className = CONTROL_PREFIX + "-conversation"; - var control = buildControl( - function () { copyConversation("markdown").then(function () { flashFeedback(bar); }); }, - function () { copyConversation("text").then(function () { flashFeedback(bar); }); } - ); - control.title = "Copy entire conversation"; - bar.appendChild(control); - document.body.appendChild(bar); // fixed-position via CSS; placement refined in Task 6 + bar.appendChild(buildControl(copyConversation, "Copy conversation")); + document.body.appendChild(bar); } catch (_) {} } - function sweep() { var b = qsa(USER_BUBBLE + "," + ASSISTANT_BUBBLE); for (var i = 0; i < b.length; i++) decorate(b[i]); } + function sweep() { + var b = qsa(USER_BUBBLE + "," + ASSISTANT_BUBBLE); + for (var i = 0; i < b.length; i++) decorate(b[i]); + installConversationControl(); + } function boot() { try { var target = (MESSAGES_CONTAINER && qs(document, MESSAGES_CONTAINER)) || document.body; sweep(); - installConversationControl(); if (typeof MutationObserver === "undefined") return; var obs = new MutationObserver(function () { sweep(); }); obs.observe(target, { childList: true, subtree: true }); @@ -693,54 +747,55 @@ _cc_md_copy_css() { cat <<'CCMDCOPYCSS' .cc-md-copy { display: inline-flex; align-items: center; - gap: 2px; vertical-align: middle; margin-left: 6px; } -.cc-md-copy-btn, -.cc-md-copy-caret { - font: inherit; - font-size: 11px; - line-height: 1.4; - padding: 1px 6px; +.cc-md-copy-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 2px; color: var(--vscode-foreground); background: transparent; - border: 1px solid var(--vscode-widget-border, transparent); + border: none; border-radius: 4px; cursor: pointer; - opacity: 0.65; + opacity: 0.6; +} +.cc-md-copy-btn svg { + display: block; + width: 14px; + height: 14px; } -.cc-md-copy-btn:hover, -.cc-md-copy-caret:hover { +.cc-md-copy-btn:hover { opacity: 1; background: var(--vscode-toolbar-hoverBackground, rgba(128, 128, 128, 0.15)); } -.cc-md-copy-menu { - position: relative; - margin-left: 4px; - padding: 2px; - background: var(--vscode-menu-background, var(--vscode-editorWidget-background)); - border: 1px solid var(--vscode-menu-border, var(--vscode-widget-border, transparent)); - border-radius: 4px; - z-index: 5; -} -.cc-md-copy-feedback { - margin-left: 6px; - font-size: 11px; - opacity: 0.85; - color: var(--vscode-foreground); +/* Success state: the icon is a green checkmark for a moment after a real copy. */ +.cc-md-copy-btn.cc-md-copy-ok, +.cc-md-copy-btn.cc-md-copy-ok:hover { + opacity: 1; + color: var(--vscode-charts-green, var(--vscode-testing-iconPassed, #89d185)); + background: transparent; } +/* Whole-conversation copy: a single floating icon pinned to the top-right corner, + clear of the chat input at the bottom. Shown only while a conversation is open + (the IIFE adds/removes it). Nudge top/right here if it crowds the sticky header. */ .cc-md-copy-conversation { position: fixed; - right: 16px; - bottom: 56px; - z-index: 10; + top: 26px; + right: 4px; + z-index: 30; + display: inline-flex; padding: 2px; background: var(--vscode-editorWidget-background); border: 1px solid var(--vscode-widget-border, transparent); border-radius: 6px; opacity: 0.85; } +.cc-md-copy-conversation .cc-md-copy { + margin-left: 0; +} .cc-md-copy-conversation:hover { opacity: 1; } diff --git a/launcher/claudemax.win.js b/launcher/claudemax.win.js index 51b6cc5..5900fa4 100644 --- a/launcher/claudemax.win.js +++ b/launcher/claudemax.win.js @@ -13,8 +13,9 @@ // this wrapper idempotently patches the extension's webview bundle on each // launch, flipping the threshold so the icon shows at any usage level. // Because it re-applies every launch, it survives extension updates. -// 3. Adds per-message and whole-conversation "Copy" controls (Markdown / plain -// text) to the VS Code chat. Like fix #2 there is no env/CLI lever, so this +// 3. Adds a single-click "Copy as Markdown" icon to every message (and a floating +// "copy conversation" icon) in the VS Code chat; the icon flips to a checkmark +// only when the copy truly lands. Like fix #2 there is no env/CLI lever, so this // wrapper idempotently appends a self-contained block to the webview bundle // (index.js + index.css) each launch; it fails safe (the controls simply do // not appear if the markup moves) and survives extension updates. @@ -291,8 +292,8 @@ function contextIconEnabled() { const MD_OPEN = "/* cc-md-copy v1 */"; const MD_CLOSE = "/* /cc-md-copy v1 */"; // >>>CCWA-MD-COPY-EMBED>>> (generated by tools/gen-embeds; do not edit) -const MD_COPY_JS = "/* cc-md-copy: per-message and whole-conversation copy (markdown/plain) for the\n * Claude Code VS Code webview. Self-contained IIFE appended to webview/index.js.\n * Additive and read-only w.r.t. app state; keyed on stable CSS-module class\n * prefixes, so it fails safe (controls simply do not appear) if a prefix moves.\n * Exposes its pure functions for node unit tests; boot()s only in a real webview. */\n/* Leading ';' so that, appended after the bundle, this IIFE can never be parsed as\n * a call on the bundle's final expression if it lacks a trailing semicolon (ASI\n * safety across extension builds). */\n;(function () {\n \"use strict\";\n\n var CONTROL_PREFIX = \"cc-md-copy\"; // every injected node's class starts with this\n var USER_BUBBLE = '[class*=\"userMessageContainer_\"]';\n // Assistant message wrapper. Verified on 2.1.170: the render emits exactly one\n // `data-testid=\"assistant-message\"` div per assistant turn, with the rating\n // widget and content blocks as its children. (The earlier `[data-message-rating]`\n // was WRONG: that attribute sits on the nested rating control, which is also only\n // rendered behind an experiment+analytics gate.) Re-pinned in Task 6.\n var ASSISTANT_BUBBLE = '[data-testid=\"assistant-message\"]';\n var MESSAGES_CONTAINER = '[class*=\"messagesContainer_\"]'; // e.g. '[class*=\"timeline_\"]'; \"\" -> observe document.body\n // Optional narrowing only. MUST be a single wrapper around ALL content blocks,\n // not a per-block class (a turn has multiple blocks). \"\" -> use the bubble itself\n // (already aggregates all blocks; sanitizeClone is the correctness gate).\n var ASSISTANT_CONTENT = \"\";\n var FEEDBACK_MS = 1800;\n\n // ---- HTML -> Markdown (DOM walk) -------------------------------------------\n // Uses only: nodeType, tagName, childNodes, textContent, getAttribute, className.\n function htmlToMarkdown(root) {\n // Longest run of consecutive backticks in s, so a code delimiter/fence can be\n // chosen longer than anything inside it (else ``` in the content closes early).\n function backtickRun(s) {\n var max = 0, cur = 0;\n for (var i = 0; i < s.length; i++) {\n if (s.charAt(i) === \"`\") { cur++; if (cur > max) max = cur; } else cur = 0;\n }\n return max;\n }\n function fence(s, min) { var n = backtickRun(s) + 1; if (n < min) n = min; return new Array(n + 1).join(\"`\"); }\n function inline(node) {\n var out = \"\";\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 3) { out += c.textContent || \"\"; continue; }\n if (c.nodeType !== 1) continue;\n var tag = (c.tagName || \"\").toUpperCase();\n if (tag === \"BR\") out += \"\\n\";\n else if (tag === \"STRONG\" || tag === \"B\") out += \"**\" + inline(c) + \"**\";\n else if (tag === \"EM\" || tag === \"I\") out += \"*\" + inline(c) + \"*\";\n else if (tag === \"DEL\" || tag === \"S\") out += \"~~\" + inline(c) + \"~~\";\n else if (tag === \"CODE\") {\n var ct = c.textContent || \"\";\n var d = fence(ct, 1);\n // CommonMark strips one leading+trailing space, so pad when an edge is a\n // backtick to keep it from merging with the delimiter.\n var p = (ct.charAt(0) === \"`\" || ct.charAt(ct.length - 1) === \"`\") ? \" \" : \"\";\n out += d + p + ct + p + d;\n }\n else if (tag === \"A\") {\n var href = c.getAttribute ? c.getAttribute(\"href\") : null;\n var t = inline(c);\n out += href ? \"[\" + t + \"](\" + href + \")\" : t;\n } else out += inline(c); // unknown inline wrapper: keep text, drop tag\n }\n return out;\n }\n function langOf(codeEl) {\n var cls = \"\";\n if (codeEl) cls = (codeEl.getAttribute && codeEl.getAttribute(\"class\")) || codeEl.className || \"\";\n var m = /language-([A-Za-z0-9+#.\\-]+)/.exec(cls || \"\");\n return m ? m[1] : \"\";\n }\n function findChildTag(node, tag) {\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n if (kids[i].nodeType === 1 && (kids[i].tagName || \"\").toUpperCase() === tag) return kids[i];\n }\n return null;\n }\n function list(node, ordered, depth) {\n var out = \"\", n = 1;\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var li = kids[i];\n if (li.nodeType !== 1 || (li.tagName || \"\").toUpperCase() !== \"LI\") continue;\n var marker = ordered ? n++ + \". \" : \"- \";\n var indent = new Array(depth + 1).join(\" \");\n var lead = \"\", nested = \"\";\n var lk = li.childNodes || [];\n for (var j = 0; j < lk.length; j++) {\n var ch = lk[j];\n var ct = ch.nodeType === 1 ? (ch.tagName || \"\").toUpperCase() : \"\";\n if (ct === \"UL\") nested += list(ch, false, depth + 1);\n else if (ct === \"OL\") nested += list(ch, true, depth + 1);\n else if (ch.nodeType === 3) lead += ch.textContent || \"\";\n else lead += inline(ch);\n }\n out += indent + marker + lead.trim() + \"\\n\" + nested;\n }\n return out;\n }\n function table(node) {\n var rows = [];\n (function collect(container) {\n var kids = container.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType !== 1) continue;\n var t = (c.tagName || \"\").toUpperCase();\n if (t === \"THEAD\" || t === \"TBODY\" || t === \"TFOOT\") collect(c);\n else if (t === \"TR\") {\n var cells = [], cc = c.childNodes || [];\n for (var j = 0; j < cc.length; j++) {\n var d = cc[j];\n if (d.nodeType !== 1) continue;\n var dt = (d.tagName || \"\").toUpperCase();\n if (dt === \"TH\" || dt === \"TD\") cells.push(inline(d).trim());\n }\n rows.push(cells);\n }\n }\n })(node);\n if (!rows.length) return \"\";\n var head = rows[0], body = rows.slice(1);\n var sep = head.map(function () { return \"---\"; });\n var out = \"| \" + head.join(\" | \") + \" |\\n| \" + sep.join(\" | \") + \" |\\n\";\n for (var k = 0; k < body.length; k++) out += \"| \" + body[k].join(\" | \") + \" |\\n\";\n return out;\n }\n function block(node) {\n var out = \"\";\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 3) { if ((c.textContent || \"\").trim()) out += c.textContent; continue; }\n if (c.nodeType !== 1) continue;\n var tag = (c.tagName || \"\").toUpperCase();\n if (/^H[1-6]$/.test(tag)) out += new Array(+tag[1] + 1).join(\"#\") + \" \" + inline(c).trim() + \"\\n\\n\";\n else if (tag === \"P\") out += inline(c).trim() + \"\\n\\n\";\n else if (tag === \"UL\") out += list(c, false, 0) + \"\\n\";\n else if (tag === \"OL\") out += list(c, true, 0) + \"\\n\";\n else if (tag === \"PRE\") {\n var code = findChildTag(c, \"CODE\");\n var lang = langOf(code || c);\n var body = (code || c).textContent || \"\";\n var f = fence(body, 3);\n out += f + lang + \"\\n\" + body.replace(/\\n$/, \"\") + \"\\n\" + f + \"\\n\\n\";\n } else if (tag === \"BLOCKQUOTE\") {\n var inner = block(c).trim().split(\"\\n\").map(function (l) { return \"> \" + l; }).join(\"\\n\");\n out += inner + \"\\n\\n\";\n } else if (tag === \"HR\") out += \"---\\n\\n\";\n else if (tag === \"TABLE\") out += table(c) + \"\\n\";\n else if (tag === \"BR\") out += \"\\n\";\n else if (tag === \"STRONG\" || tag === \"B\" || tag === \"EM\" || tag === \"I\" ||\n tag === \"A\" || tag === \"CODE\" || tag === \"DEL\" || tag === \"S\")\n out += inline(c) + \"\\n\\n\";\n else out += block(c); // unknown wrapper: recurse (drop tag, keep content)\n }\n return out;\n }\n // block() dispatches on each CHILD's tag, treating the passed node as a plain\n // container. Wrap root in a one-off container so root's OWN tag is dispatched\n // too: callers pass either the bubble container (its block children render) or\n // a single block element like
    /
      /
    (now handled, not flattened).\n return block({ childNodes: [root] }).replace(/\\n{3,}/g, \"\\n\\n\").trim();\n }\n\n // ---- pure helpers ----------------------------------------------------------\n function hasPrefix(node, prefix) {\n if (node.nodeType !== 1 || typeof node.className !== \"string\") return false;\n var parts = node.className.split(/\\s+/);\n for (var i = 0; i < parts.length; i++) if (parts[i].indexOf(prefix) === 0) return true;\n return false;\n }\n\n // Class-prefix hooks for non-content chrome that renders *inside* an assistant\n // bubble (verified on 2.1.170; Task 6 re-pins these). tool*/thinking_ are the v1\n // exclusions; unknownContent_ is the renderer's fallback for unrecognized block\n // types, so stripping it makes a *future* block type fail safe to excluded rather\n // than leaking \"Unsupported content\" into the copy. Re-pin if a prefix moves.\n var CHROME_PREFIXES = [\"toolUse_\", \"toolResult_\", \"toolReference_\", \"thinking_\", \"unknownContent_\"];\n\n // True for any node that must never appear in copied output: our own controls,\n // the rating widget (`data-message-rating` + its \"Thanks for your feedback\"\n // text), any button (copy-code chrome), and the excluded content blocks above.\n function isChrome(node) {\n if (node.nodeType !== 1) return false;\n if ((node.tagName || \"\").toUpperCase() === \"BUTTON\") return true;\n if (node.getAttribute && node.getAttribute(\"data-message-rating\") !== null) return true;\n if (hasPrefix(node, CONTROL_PREFIX)) return true;\n for (var i = 0; i < CHROME_PREFIXES.length; i++) if (hasPrefix(node, CHROME_PREFIXES[i])) return true;\n return false;\n }\n\n // Deep-clone `contentNode`, then strip every chrome node so copied output is the\n // message's text content only. This is a CORRECTNESS GATE, not cosmetic: the\n // default content node is the whole bubble (all content-block siblings, so multi-\n // block assistant turns are captured), and this strip-list is the only thing\n // keeping the rating widget and v1-excluded blocks out of the copy.\n function sanitizeClone(contentNode) {\n var clone = contentNode.cloneNode(true);\n (function strip(node) {\n var kids = (node.childNodes || []).slice();\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 1 && isChrome(c)) { node.removeChild(c); continue; }\n if (c.nodeType === 1) strip(c);\n }\n })(clone);\n return clone;\n }\n\n function classifyBubble(node) {\n if (node.nodeType !== 1) return null;\n if (hasPrefix(node, \"userMessageContainer_\")) return \"user\";\n if (node.getAttribute && node.getAttribute(\"data-testid\") === \"assistant-message\") return \"assistant\";\n return null;\n }\n\n // Build the whole-conversation markdown from an ordered list of bubbles.\n // `contentOf(bubble)` resolves the content node (default: the bubble itself, so\n // every content block is included; sanitizeClone drops chrome); a default is\n // provided for tests.\n function conversationToMarkdown(bubbles, contentOf) {\n contentOf = contentOf || function (b) { return b; };\n var parts = [];\n for (var i = 0; i < bubbles.length; i++) {\n var role = classifyBubble(bubbles[i]);\n if (!role) continue;\n var clean = sanitizeClone(contentOf(bubbles[i]));\n var body = role === \"assistant\" ? htmlToMarkdown(clean) : (clean.textContent || \"\").trim();\n if (!body) continue;\n parts.push((role === \"user\" ? \"## User\" : \"## Assistant\") + \"\\n\\n\" + body);\n }\n return parts.join(\"\\n\\n\") + (parts.length ? \"\\n\" : \"\");\n }\n\n // ---- exports (node tests) / boot (real webview) ----------------------------\n if (typeof document !== \"undefined\") {\n boot();\n } else if (typeof module !== \"undefined\" && module.exports) {\n module.exports = { htmlToMarkdown: htmlToMarkdown, sanitizeClone: sanitizeClone,\n classifyBubble: classifyBubble, conversationToMarkdown: conversationToMarkdown };\n }\n\n // ---- live-webview wiring (runs only when a document exists) ----------------\n function qs(node, sel) { try { return sel && node.querySelector ? node.querySelector(sel) : null; } catch (_) { return null; } }\n function qsa(sel) { try { return Array.prototype.slice.call(document.querySelectorAll(sel)); } catch (_) { return []; } }\n\n // The content node to convert/copy: the optional ASSISTANT_CONTENT wrapper if\n // pinned and present, else the bubble itself. The bubble already contains every\n // content-block sibling of a multi-block turn, and sanitizeClone strips the\n // chrome (rating widget, tool/thinking/unknown blocks, buttons, our controls)\n // either way -- so this is a narrowing, never the thing that guarantees\n // correctness.\n function contentNodeOf(bubble, role) {\n if (role === \"assistant\" && ASSISTANT_CONTENT) {\n var n = qs(bubble, ASSISTANT_CONTENT);\n if (n) return n;\n }\n return bubble;\n }\n\n function copyText(text) {\n try {\n if (navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(text);\n } catch (_) {}\n return Promise.resolve(); // best-effort; never throw into the app\n }\n\n function flashFeedback(host) {\n try {\n var fb = document.createElement(\"span\");\n fb.className = CONTROL_PREFIX + \"-feedback\";\n fb.textContent = \"Copied\";\n host.appendChild(fb);\n setTimeout(function () { if (fb && fb.parentNode) fb.parentNode.removeChild(fb); }, FEEDBACK_MS);\n } catch (_) {}\n }\n\n function bubbleMarkdown(bubble, role) {\n var clean = sanitizeClone(contentNodeOf(bubble, role));\n return role === \"assistant\" ? htmlToMarkdown(clean) : (clean.textContent || \"\").trim();\n }\n function bubblePlain(bubble, role) {\n return (sanitizeClone(contentNodeOf(bubble, role)).textContent || \"\").trim();\n }\n\n // Build a single control: a primary \"Copy\" (markdown) plus a small caret that\n // toggles a menu with \"Copy as plain text\". All nodes carry the CONTROL_PREFIX\n // class so sanitizeClone removes them from any copied content.\n function buildControl(onMarkdown, onPlain) {\n var wrap = document.createElement(\"span\");\n wrap.className = CONTROL_PREFIX;\n var primary = document.createElement(\"button\");\n primary.type = \"button\";\n primary.className = CONTROL_PREFIX + \"-btn\";\n primary.title = \"Copy as Markdown\";\n primary.textContent = \"Copy\";\n primary.addEventListener(\"click\", function (e) { e.stopPropagation(); onMarkdown(primary); });\n var caret = document.createElement(\"button\");\n caret.type = \"button\";\n caret.className = CONTROL_PREFIX + \"-caret\";\n caret.title = \"Copy options\";\n caret.textContent = \"\u25be\"; // black down-pointing small triangle\n var menu = document.createElement(\"span\");\n menu.className = CONTROL_PREFIX + \"-menu\";\n menu.style.display = \"none\";\n var plain = document.createElement(\"button\");\n plain.type = \"button\";\n plain.className = CONTROL_PREFIX + \"-btn\";\n plain.textContent = \"Copy as plain text\";\n plain.addEventListener(\"click\", function (e) { e.stopPropagation(); menu.style.display = \"none\"; onPlain(plain); });\n menu.appendChild(plain);\n caret.addEventListener(\"click\", function (e) {\n e.stopPropagation();\n menu.style.display = menu.style.display === \"none\" ? \"inline-block\" : \"none\";\n });\n wrap.appendChild(primary);\n wrap.appendChild(caret);\n wrap.appendChild(menu);\n return wrap;\n }\n\n function decorate(bubble) {\n try {\n var role = classifyBubble(bubble);\n if (!role) return;\n if (qs(bubble, \".\" + CONTROL_PREFIX)) return; // already decorated\n var control = buildControl(\n function (host) { copyText(bubbleMarkdown(bubble, role)).then(function () { flashFeedback(control); }); },\n function (host) { copyText(bubblePlain(bubble, role)).then(function () { flashFeedback(control); }); }\n );\n bubble.appendChild(control);\n } catch (_) {}\n }\n\n function copyConversation(format) {\n var bubbles = qsa(USER_BUBBLE + \",\" + ASSISTANT_BUBBLE);\n if (format === \"text\") {\n var lines = [];\n for (var i = 0; i < bubbles.length; i++) {\n var role = classifyBubble(bubbles[i]);\n if (!role) continue;\n var body = bubblePlain(bubbles[i], role);\n if (body) lines.push(body);\n }\n return copyText(lines.join(\"\\n\\n\") + (lines.length ? \"\\n\" : \"\"));\n }\n return copyText(conversationToMarkdown(bubbles, function (b) {\n return contentNodeOf(b, classifyBubble(b));\n }));\n }\n\n function installConversationControl() {\n try {\n if (qs(document, \".\" + CONTROL_PREFIX + \"-conversation\")) return;\n var bar = document.createElement(\"div\");\n bar.className = CONTROL_PREFIX + \"-conversation\";\n var control = buildControl(\n function () { copyConversation(\"markdown\").then(function () { flashFeedback(bar); }); },\n function () { copyConversation(\"text\").then(function () { flashFeedback(bar); }); }\n );\n control.title = \"Copy entire conversation\";\n bar.appendChild(control);\n document.body.appendChild(bar); // fixed-position via CSS; placement refined in Task 6\n } catch (_) {}\n }\n\n function sweep() { var b = qsa(USER_BUBBLE + \",\" + ASSISTANT_BUBBLE); for (var i = 0; i < b.length; i++) decorate(b[i]); }\n\n function boot() {\n try {\n var target = (MESSAGES_CONTAINER && qs(document, MESSAGES_CONTAINER)) || document.body;\n sweep();\n installConversationControl();\n if (typeof MutationObserver === \"undefined\") return;\n var obs = new MutationObserver(function () { sweep(); });\n obs.observe(target, { childList: true, subtree: true });\n } catch (_) {}\n }\n})();\n"; -const MD_COPY_CSS = ".cc-md-copy {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n vertical-align: middle;\n margin-left: 6px;\n}\n.cc-md-copy-btn,\n.cc-md-copy-caret {\n font: inherit;\n font-size: 11px;\n line-height: 1.4;\n padding: 1px 6px;\n color: var(--vscode-foreground);\n background: transparent;\n border: 1px solid var(--vscode-widget-border, transparent);\n border-radius: 4px;\n cursor: pointer;\n opacity: 0.65;\n}\n.cc-md-copy-btn:hover,\n.cc-md-copy-caret:hover {\n opacity: 1;\n background: var(--vscode-toolbar-hoverBackground, rgba(128, 128, 128, 0.15));\n}\n.cc-md-copy-menu {\n position: relative;\n margin-left: 4px;\n padding: 2px;\n background: var(--vscode-menu-background, var(--vscode-editorWidget-background));\n border: 1px solid var(--vscode-menu-border, var(--vscode-widget-border, transparent));\n border-radius: 4px;\n z-index: 5;\n}\n.cc-md-copy-feedback {\n margin-left: 6px;\n font-size: 11px;\n opacity: 0.85;\n color: var(--vscode-foreground);\n}\n.cc-md-copy-conversation {\n position: fixed;\n right: 16px;\n bottom: 56px;\n z-index: 10;\n padding: 2px;\n background: var(--vscode-editorWidget-background);\n border: 1px solid var(--vscode-widget-border, transparent);\n border-radius: 6px;\n opacity: 0.85;\n}\n.cc-md-copy-conversation:hover {\n opacity: 1;\n}\n"; +const MD_COPY_JS = "/* cc-md-copy: per-message and whole-conversation copy (Markdown) for the\n * Claude Code VS Code webview. Self-contained IIFE appended to webview/index.js.\n * Each control is a single clipboard icon that flips to a checkmark for ~2s when a\n * copy actually succeeds (no text label, no menu). Additive and read-only w.r.t.\n * app state; keyed on stable CSS-module class prefixes, so it fails safe (controls\n * simply do not appear) if a prefix moves.\n * Exposes its pure functions for node unit tests; boot()s only in a real webview. */\n/* Leading ';' so that, appended after the bundle, this IIFE can never be parsed as\n * a call on the bundle's final expression if it lacks a trailing semicolon (ASI\n * safety across extension builds). */\n;(function () {\n \"use strict\";\n\n var CONTROL_PREFIX = \"cc-md-copy\"; // every injected node's class starts with this\n var USER_BUBBLE = '[class*=\"userMessageContainer_\"]';\n // Assistant message wrapper. Verified on 2.1.170: the render emits exactly one\n // `data-testid=\"assistant-message\"` div per assistant turn, with the rating\n // widget and content blocks as its children. (The earlier `[data-message-rating]`\n // was WRONG: that attribute sits on the nested rating control, which is also only\n // rendered behind an experiment+analytics gate.) Re-pinned in Task 6.\n var ASSISTANT_BUBBLE = '[data-testid=\"assistant-message\"]';\n var MESSAGES_CONTAINER = '[class*=\"messagesContainer_\"]'; // e.g. '[class*=\"timeline_\"]'; \"\" -> observe document.body\n // Optional narrowing only. MUST be a single wrapper around ALL content blocks,\n // not a per-block class (a turn has multiple blocks). \"\" -> use the bubble itself\n // (already aggregates all blocks; sanitizeClone is the correctness gate).\n var ASSISTANT_CONTENT = \"\";\n var FEEDBACK_MS = 2000; // how long the checkmark shows after a successful copy\n\n // ---- HTML -> Markdown (DOM walk) -------------------------------------------\n // Uses only: nodeType, tagName, childNodes, textContent, getAttribute, className.\n function htmlToMarkdown(root) {\n // Longest run of consecutive backticks in s, so a code delimiter/fence can be\n // chosen longer than anything inside it (else ``` in the content closes early).\n function backtickRun(s) {\n var max = 0, cur = 0;\n for (var i = 0; i < s.length; i++) {\n if (s.charAt(i) === \"`\") { cur++; if (cur > max) max = cur; } else cur = 0;\n }\n return max;\n }\n function fence(s, min) { var n = backtickRun(s) + 1; if (n < min) n = min; return new Array(n + 1).join(\"`\"); }\n function inline(node) {\n var out = \"\";\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 3) { out += c.textContent || \"\"; continue; }\n if (c.nodeType !== 1) continue;\n var tag = (c.tagName || \"\").toUpperCase();\n if (tag === \"BR\") out += \"\\n\";\n else if (tag === \"STRONG\" || tag === \"B\") out += \"**\" + inline(c) + \"**\";\n else if (tag === \"EM\" || tag === \"I\") out += \"*\" + inline(c) + \"*\";\n else if (tag === \"DEL\" || tag === \"S\") out += \"~~\" + inline(c) + \"~~\";\n else if (tag === \"CODE\") {\n var ct = c.textContent || \"\";\n var d = fence(ct, 1);\n // CommonMark strips one leading+trailing space, so pad when an edge is a\n // backtick to keep it from merging with the delimiter.\n var p = (ct.charAt(0) === \"`\" || ct.charAt(ct.length - 1) === \"`\") ? \" \" : \"\";\n out += d + p + ct + p + d;\n }\n else if (tag === \"A\") {\n var href = c.getAttribute ? c.getAttribute(\"href\") : null;\n var t = inline(c);\n out += href ? \"[\" + t + \"](\" + href + \")\" : t;\n } else out += inline(c); // unknown inline wrapper: keep text, drop tag\n }\n return out;\n }\n function langOf(codeEl) {\n var cls = \"\";\n if (codeEl) cls = (codeEl.getAttribute && codeEl.getAttribute(\"class\")) || codeEl.className || \"\";\n var m = /language-([A-Za-z0-9+#.\\-]+)/.exec(cls || \"\");\n return m ? m[1] : \"\";\n }\n function findChildTag(node, tag) {\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n if (kids[i].nodeType === 1 && (kids[i].tagName || \"\").toUpperCase() === tag) return kids[i];\n }\n return null;\n }\n function list(node, ordered, depth) {\n var out = \"\", n = 1;\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var li = kids[i];\n if (li.nodeType !== 1 || (li.tagName || \"\").toUpperCase() !== \"LI\") continue;\n var marker = ordered ? n++ + \". \" : \"- \";\n var indent = new Array(depth + 1).join(\" \");\n var lead = \"\", nested = \"\";\n var lk = li.childNodes || [];\n for (var j = 0; j < lk.length; j++) {\n var ch = lk[j];\n var ct = ch.nodeType === 1 ? (ch.tagName || \"\").toUpperCase() : \"\";\n if (ct === \"UL\") nested += list(ch, false, depth + 1);\n else if (ct === \"OL\") nested += list(ch, true, depth + 1);\n else if (ch.nodeType === 3) lead += ch.textContent || \"\";\n else lead += inline(ch);\n }\n out += indent + marker + lead.trim() + \"\\n\" + nested;\n }\n return out;\n }\n function table(node) {\n var rows = [];\n (function collect(container) {\n var kids = container.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType !== 1) continue;\n var t = (c.tagName || \"\").toUpperCase();\n if (t === \"THEAD\" || t === \"TBODY\" || t === \"TFOOT\") collect(c);\n else if (t === \"TR\") {\n var cells = [], cc = c.childNodes || [];\n for (var j = 0; j < cc.length; j++) {\n var d = cc[j];\n if (d.nodeType !== 1) continue;\n var dt = (d.tagName || \"\").toUpperCase();\n if (dt === \"TH\" || dt === \"TD\") cells.push(inline(d).trim());\n }\n rows.push(cells);\n }\n }\n })(node);\n if (!rows.length) return \"\";\n var head = rows[0], body = rows.slice(1);\n var sep = head.map(function () { return \"---\"; });\n var out = \"| \" + head.join(\" | \") + \" |\\n| \" + sep.join(\" | \") + \" |\\n\";\n for (var k = 0; k < body.length; k++) out += \"| \" + body[k].join(\" | \") + \" |\\n\";\n return out;\n }\n function block(node) {\n var out = \"\";\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 3) { if ((c.textContent || \"\").trim()) out += c.textContent; continue; }\n if (c.nodeType !== 1) continue;\n var tag = (c.tagName || \"\").toUpperCase();\n if (/^H[1-6]$/.test(tag)) out += new Array(+tag[1] + 1).join(\"#\") + \" \" + inline(c).trim() + \"\\n\\n\";\n else if (tag === \"P\") out += inline(c).trim() + \"\\n\\n\";\n else if (tag === \"UL\") out += list(c, false, 0) + \"\\n\";\n else if (tag === \"OL\") out += list(c, true, 0) + \"\\n\";\n else if (tag === \"PRE\") {\n var code = findChildTag(c, \"CODE\");\n var lang = langOf(code || c);\n var body = (code || c).textContent || \"\";\n var f = fence(body, 3);\n out += f + lang + \"\\n\" + body.replace(/\\n$/, \"\") + \"\\n\" + f + \"\\n\\n\";\n } else if (tag === \"BLOCKQUOTE\") {\n var inner = block(c).trim().split(\"\\n\").map(function (l) { return \"> \" + l; }).join(\"\\n\");\n out += inner + \"\\n\\n\";\n } else if (tag === \"HR\") out += \"---\\n\\n\";\n else if (tag === \"TABLE\") out += table(c) + \"\\n\";\n else if (tag === \"BR\") out += \"\\n\";\n else if (tag === \"STRONG\" || tag === \"B\" || tag === \"EM\" || tag === \"I\" ||\n tag === \"A\" || tag === \"CODE\" || tag === \"DEL\" || tag === \"S\")\n out += inline(c) + \"\\n\\n\";\n else out += block(c); // unknown wrapper: recurse (drop tag, keep content)\n }\n return out;\n }\n // block() dispatches on each CHILD's tag, treating the passed node as a plain\n // container. Wrap root in a one-off container so root's OWN tag is dispatched\n // too: callers pass either the bubble container (its block children render) or\n // a single block element like
    /
      /
    (now handled, not flattened).\n return block({ childNodes: [root] }).replace(/\\n{3,}/g, \"\\n\\n\").trim();\n }\n\n // ---- pure helpers ----------------------------------------------------------\n function hasPrefix(node, prefix) {\n if (node.nodeType !== 1 || typeof node.className !== \"string\") return false;\n var parts = node.className.split(/\\s+/);\n for (var i = 0; i < parts.length; i++) if (parts[i].indexOf(prefix) === 0) return true;\n return false;\n }\n\n // Class-prefix hooks for non-content chrome that renders *inside* an assistant\n // bubble (verified on 2.1.170; Task 6 re-pins these). tool*/thinking_ are the v1\n // exclusions; unknownContent_ is the renderer's fallback for unrecognized block\n // types, so stripping it makes a *future* block type fail safe to excluded rather\n // than leaking \"Unsupported content\" into the copy. Re-pin if a prefix moves.\n var CHROME_PREFIXES = [\"toolUse_\", \"toolResult_\", \"toolReference_\", \"thinking_\", \"unknownContent_\"];\n\n // True for any node that must never appear in copied output: our own controls,\n // the rating widget (`data-message-rating` + its \"Thanks for your feedback\"\n // text), any button (copy-code chrome), and the excluded content blocks above.\n function isChrome(node) {\n if (node.nodeType !== 1) return false;\n if ((node.tagName || \"\").toUpperCase() === \"BUTTON\") return true;\n if (node.getAttribute && node.getAttribute(\"data-message-rating\") !== null) return true;\n if (hasPrefix(node, CONTROL_PREFIX)) return true;\n for (var i = 0; i < CHROME_PREFIXES.length; i++) if (hasPrefix(node, CHROME_PREFIXES[i])) return true;\n return false;\n }\n\n // Deep-clone `contentNode`, then strip every chrome node so copied output is the\n // message's text content only. This is a CORRECTNESS GATE, not cosmetic: the\n // default content node is the whole bubble (all content-block siblings, so multi-\n // block assistant turns are captured), and this strip-list is the only thing\n // keeping the rating widget and v1-excluded blocks out of the copy.\n function sanitizeClone(contentNode) {\n var clone = contentNode.cloneNode(true);\n (function strip(node) {\n var kids = (node.childNodes || []).slice();\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 1 && isChrome(c)) { node.removeChild(c); continue; }\n if (c.nodeType === 1) strip(c);\n }\n })(clone);\n return clone;\n }\n\n function classifyBubble(node) {\n if (node.nodeType !== 1) return null;\n if (hasPrefix(node, \"userMessageContainer_\")) return \"user\";\n if (node.getAttribute && node.getAttribute(\"data-testid\") === \"assistant-message\") return \"assistant\";\n return null;\n }\n\n // Build the whole-conversation markdown from an ordered list of bubbles.\n // `contentOf(bubble)` resolves the content node (default: the bubble itself, so\n // every content block is included; sanitizeClone drops chrome); a default is\n // provided for tests.\n function conversationToMarkdown(bubbles, contentOf) {\n contentOf = contentOf || function (b) { return b; };\n var parts = [];\n for (var i = 0; i < bubbles.length; i++) {\n var role = classifyBubble(bubbles[i]);\n if (!role) continue;\n var clean = sanitizeClone(contentOf(bubbles[i]));\n var body = role === \"assistant\" ? htmlToMarkdown(clean) : (clean.textContent || \"\").trim();\n if (!body) continue;\n parts.push((role === \"user\" ? \"## User\" : \"## Assistant\") + \"\\n\\n\" + body);\n }\n return parts.join(\"\\n\\n\") + (parts.length ? \"\\n\" : \"\");\n }\n\n // ---- exports (node tests) / boot (real webview) ----------------------------\n if (typeof document !== \"undefined\") {\n boot();\n } else if (typeof module !== \"undefined\" && module.exports) {\n module.exports = { htmlToMarkdown: htmlToMarkdown, sanitizeClone: sanitizeClone,\n classifyBubble: classifyBubble, conversationToMarkdown: conversationToMarkdown,\n copyText: copyText };\n }\n\n // ---- live-webview wiring (runs only when a document exists) ----------------\n function qs(node, sel) { try { return sel && node.querySelector ? node.querySelector(sel) : null; } catch (_) { return null; } }\n function qsa(sel) { try { return Array.prototype.slice.call(document.querySelectorAll(sel)); } catch (_) { return []; } }\n\n // The content node to convert/copy: the optional ASSISTANT_CONTENT wrapper if\n // pinned and present, else the bubble itself. The bubble already contains every\n // content-block sibling of a multi-block turn, and sanitizeClone strips the\n // chrome (rating widget, tool/thinking/unknown blocks, buttons, our controls)\n // either way -- so this is a narrowing, never the thing that guarantees\n // correctness.\n function contentNodeOf(bubble, role) {\n if (role === \"assistant\" && ASSISTANT_CONTENT) {\n var n = qs(bubble, ASSISTANT_CONTENT);\n if (n) return n;\n }\n return bubble;\n }\n\n // Copy `s` via a synchronous execCommand(\"copy\") on an off-screen textarea, and\n // report whether it actually happened. Done first (and synchronously) because it\n // runs inside the click gesture and works whether or not the page is a secure\n // context -- so it covers remote / code-server, where the async Clipboard API is\n // simply absent. Restores the prior selection/focus so it is invisible.\n function execCopy(s) {\n try {\n if (typeof document === \"undefined\" || !document.createElement) return false;\n var prev = document.activeElement || null;\n var sel = document.getSelection ? document.getSelection() : null;\n var saved = (sel && sel.rangeCount) ? sel.getRangeAt(0) : null;\n var ta = document.createElement(\"textarea\");\n ta.value = s;\n ta.setAttribute(\"readonly\", \"\");\n ta.style.position = \"fixed\";\n ta.style.top = \"-1000px\";\n ta.style.left = \"0\";\n ta.style.opacity = \"0\";\n (document.body || document.documentElement).appendChild(ta);\n ta.focus();\n ta.select();\n var ok = false;\n try { ok = document.execCommand(\"copy\"); } catch (_) { ok = false; }\n if (ta.parentNode) ta.parentNode.removeChild(ta);\n if (saved && sel) { try { sel.removeAllRanges(); sel.addRange(saved); } catch (_) {} }\n if (prev && prev.focus) { try { prev.focus(); } catch (_) {} }\n return !!ok;\n } catch (_) { return false; }\n }\n\n // Copy `text` and resolve to whether the copy ACTUALLY happened, so callers only\n // show success on a real copy -- never a false \"copied\" (the original bug:\n // navigator.clipboard was undefined in the webview, the code fell through to\n // Promise.resolve(), and the UI claimed success while nothing was written). Empty\n // text is a non-copy -> false. execCommand first (gesture-safe, secure-context-\n // independent); the async Clipboard API is the fallback. Never throws.\n function copyText(text) {\n var s = (text == null) ? \"\" : String(text);\n if (!s) return Promise.resolve(false);\n if (execCopy(s)) return Promise.resolve(true);\n try {\n if (typeof navigator !== \"undefined\" && navigator.clipboard && navigator.clipboard.writeText) {\n return navigator.clipboard.writeText(s).then(\n function () { return true; },\n function () { return false; }\n );\n }\n } catch (_) {}\n return Promise.resolve(false);\n }\n\n function bubbleMarkdown(bubble, role) {\n var clean = sanitizeClone(contentNodeOf(bubble, role));\n return role === \"assistant\" ? htmlToMarkdown(clean) : (clean.textContent || \"\").trim();\n }\n\n // Inline SVG icons (currentColor, ~14px). Set via innerHTML on our own buttons\n // only; the markup never reaches copied content (sanitizeClone drops our nodes).\n var ICON_COPY = '';\n var ICON_CHECK = '';\n\n // Flip the button to a checkmark for FEEDBACK_MS, then restore. Idempotent across\n // rapid clicks (any pending restore is cleared first).\n function showCopied(btn) {\n try {\n if (btn.__ccTimer) clearTimeout(btn.__ccTimer);\n btn.classList.add(CONTROL_PREFIX + \"-ok\");\n btn.innerHTML = ICON_CHECK;\n btn.__ccTimer = setTimeout(function () {\n try { btn.classList.remove(CONTROL_PREFIX + \"-ok\"); btn.innerHTML = ICON_COPY; } catch (_) {}\n btn.__ccTimer = null;\n }, FEEDBACK_MS);\n } catch (_) {}\n }\n\n // Build a single control: one clipboard-icon button. `onCopy()` is invoked\n // synchronously on click (so the copy stays inside the user gesture) and must\n // return a Promise; the checkmark shows only when it resolves true. All\n // nodes carry the CONTROL_PREFIX class so sanitizeClone strips them from copies.\n function buildControl(onCopy, title) {\n var wrap = document.createElement(\"span\");\n wrap.className = CONTROL_PREFIX;\n var btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = CONTROL_PREFIX + \"-btn\";\n btn.title = title || \"Copy as Markdown\";\n btn.setAttribute(\"aria-label\", btn.title);\n btn.innerHTML = ICON_COPY;\n var busy = false;\n btn.addEventListener(\"click\", function (e) {\n e.stopPropagation();\n if (busy) return;\n busy = true;\n var p;\n try { p = onCopy(); } catch (_) { p = false; }\n Promise.resolve(p).then(\n function (ok) { busy = false; if (ok) showCopied(btn); },\n function () { busy = false; }\n );\n });\n wrap.appendChild(btn);\n return wrap;\n }\n\n function decorate(bubble) {\n try {\n var role = classifyBubble(bubble);\n if (!role) return;\n // Idempotent: keep exactly one control. A React re-render of the bubble can\n // leave a stale control behind or transiently defeat an \"already decorated\"\n // guard, which is what produced duplicate rows of buttons; prune any extras\n // every sweep and only add one when none remain.\n var existing = bubble.querySelectorAll ? bubble.querySelectorAll(\".\" + CONTROL_PREFIX) : null;\n if (existing && existing.length) {\n for (var i = existing.length - 1; i >= 1; i--) {\n if (existing[i] && existing[i].parentNode) existing[i].parentNode.removeChild(existing[i]);\n }\n return;\n }\n var control = buildControl(function () {\n return copyText(bubbleMarkdown(bubble, role));\n }, \"Copy as Markdown\");\n bubble.appendChild(control);\n } catch (_) {}\n }\n\n function copyConversation() {\n var bubbles = qsa(USER_BUBBLE + \",\" + ASSISTANT_BUBBLE);\n return copyText(conversationToMarkdown(bubbles, function (b) {\n return contentNodeOf(b, classifyBubble(b));\n }));\n }\n\n // A single floating \"Copy conversation\" icon, present only while a conversation\n // is open (so it never clutters the history-list view). Pinned top-right by CSS,\n // clear of the chat input at the bottom; the most-recent-prompt sticky header\n // sits to its left.\n function installConversationControl() {\n try {\n var existing = qs(document, \".\" + CONTROL_PREFIX + \"-conversation\");\n var hasMessages = qsa(USER_BUBBLE + \",\" + ASSISTANT_BUBBLE).length > 0;\n if (!hasMessages) {\n if (existing && existing.parentNode) existing.parentNode.removeChild(existing);\n return;\n }\n if (existing) return;\n var bar = document.createElement(\"div\");\n bar.className = CONTROL_PREFIX + \"-conversation\";\n bar.appendChild(buildControl(copyConversation, \"Copy conversation\"));\n document.body.appendChild(bar);\n } catch (_) {}\n }\n\n function sweep() {\n var b = qsa(USER_BUBBLE + \",\" + ASSISTANT_BUBBLE);\n for (var i = 0; i < b.length; i++) decorate(b[i]);\n installConversationControl();\n }\n\n function boot() {\n try {\n var target = (MESSAGES_CONTAINER && qs(document, MESSAGES_CONTAINER)) || document.body;\n sweep();\n if (typeof MutationObserver === \"undefined\") return;\n var obs = new MutationObserver(function () { sweep(); });\n obs.observe(target, { childList: true, subtree: true });\n } catch (_) {}\n }\n})();\n"; +const MD_COPY_CSS = ".cc-md-copy {\n display: inline-flex;\n align-items: center;\n vertical-align: middle;\n margin-left: 6px;\n}\n.cc-md-copy-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 2px;\n color: var(--vscode-foreground);\n background: transparent;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n opacity: 0.6;\n}\n.cc-md-copy-btn svg {\n display: block;\n width: 14px;\n height: 14px;\n}\n.cc-md-copy-btn:hover {\n opacity: 1;\n background: var(--vscode-toolbar-hoverBackground, rgba(128, 128, 128, 0.15));\n}\n/* Success state: the icon is a green checkmark for a moment after a real copy. */\n.cc-md-copy-btn.cc-md-copy-ok,\n.cc-md-copy-btn.cc-md-copy-ok:hover {\n opacity: 1;\n color: var(--vscode-charts-green, var(--vscode-testing-iconPassed, #89d185));\n background: transparent;\n}\n/* Whole-conversation copy: a single floating icon pinned to the top-right corner,\n clear of the chat input at the bottom. Shown only while a conversation is open\n (the IIFE adds/removes it). Nudge top/right here if it crowds the sticky header. */\n.cc-md-copy-conversation {\n position: fixed;\n top: 26px;\n right: 4px;\n z-index: 30;\n display: inline-flex;\n padding: 2px;\n background: var(--vscode-editorWidget-background);\n border: 1px solid var(--vscode-widget-border, transparent);\n border-radius: 6px;\n opacity: 0.85;\n}\n.cc-md-copy-conversation .cc-md-copy {\n margin-left: 0;\n}\n.cc-md-copy-conversation:hover {\n opacity: 1;\n}\n"; // << it does not boot()); copyText reads the globals +# at call time. exec_ok / write controls each path; `captured` records the text +# each mechanism received so we can assert the real payload, not just the verdict. +HARNESS = """ +const M = require(%(mod)s); +const cfg = %(cfg)s; +const captured = { exec: null, api: null }; +// defineProperty (not assignment): require() already ran with document undefined so +// the module took its export branch; and node's built-in `navigator` global is a +// getter (no setter), so a plain `global.navigator =` would silently no-op. +const DOC = { + activeElement: null, + getSelection: () => null, + createElement: (t) => ({ + tagName: t, value: "", style: {}, parentNode: null, + setAttribute() {}, focus() {}, + select() { captured.exec = this.value; }, + }), + documentElement: {}, + execCommand: () => cfg.exec_ok, +}; +DOC.body = { appendChild(n) { n.parentNode = DOC.body; }, removeChild(n) { n.parentNode = null; } }; +const NAV = cfg.clipboard === "absent" ? {} : { clipboard: { writeText: (s) => { + captured.api = s; + return cfg.clipboard === "resolve" ? Promise.resolve() : Promise.reject(new Error("blocked")); +} } }; +Object.defineProperty(globalThis, "document", { value: DOC, configurable: true, writable: true }); +Object.defineProperty(globalThis, "navigator", { value: NAV, configurable: true, writable: true }); +Promise.resolve(M.copyText(cfg.text)).then((ok) => { + process.stdout.write(JSON.stringify({ ok, captured })); +}); +""" + + +def run(text, exec_ok, clipboard): + cfg = {"text": text, "exec_ok": exec_ok, "clipboard": clipboard} + script = HARNESS % {"mod": json.dumps(str(INJECT_JS)), "cfg": json.dumps(cfg)} + res = subprocess.run(["node", "-e", script], cwd=REPO, text=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=10) + assert res.returncode == 0, res.stderr + return json.loads(res.stdout) + + +class CopyTextHonestyTests(unittest.TestCase): + def test_empty_text_is_never_a_copy(self): + out = run("", exec_ok=True, clipboard="resolve") + self.assertFalse(out["ok"]) + self.assertIsNone(out["captured"]["exec"]) # never even attempted + self.assertIsNone(out["captured"]["api"]) + + def test_execcommand_path_succeeds_and_carries_the_text(self): + out = run("hello world", exec_ok=True, clipboard="resolve") + self.assertTrue(out["ok"]) + self.assertEqual(out["captured"]["exec"], "hello world") + self.assertIsNone(out["captured"]["api"]) # API never needed + + def test_falls_back_to_clipboard_api_when_execcommand_fails(self): + out = run("payload", exec_ok=False, clipboard="resolve") + self.assertTrue(out["ok"]) + self.assertEqual(out["captured"]["api"], "payload") + + def test_no_false_success_when_clipboard_absent_and_exec_fails(self): + # The exact original-bug environment: no Clipboard API, execCommand unusable. + out = run("payload", exec_ok=False, clipboard="absent") + self.assertFalse(out["ok"]) + + def test_no_false_success_when_clipboard_api_rejects(self): + out = run("payload", exec_ok=False, clipboard="reject") + self.assertFalse(out["ok"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_md_export.py b/tests/test_md_export.py index ed1390c..4d052c5 100644 --- a/tests/test_md_export.py +++ b/tests/test_md_export.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 -"""Tests for cc-export.py: session resolution, markdown/text rendering, opt-in -thinking/tools, and --open. Synthetic JSONL pins the documented row schema -without needing a real transcript.""" +"""Tests for cc-export.py: session resolution, markdown/text rendering, and +opt-in thinking/tools. Synthetic JSONL pins the documented row schema without +needing a real transcript.""" import importlib.util import json import os import pathlib -import stat import tempfile import unittest @@ -113,34 +112,11 @@ def test_resolve_missing_returns_none(self): self.mod.resolve_session(config=str(config), cwd=str(cwd), session_id="ghost") ) - def test_open_invokes_editor_with_resolved_path(self): - # Stub `code` on PATH; assert main(--open) calls it with the jsonl path. - with tempfile.TemporaryDirectory() as td: - config = pathlib.Path(td) / "config" - projects = config / "projects" - cwd = pathlib.Path(td) / "work" / "proj" - cwd.mkdir(parents=True) - key = self.mod.project_key_for_cwd(str(cwd)) - sess = write_session(projects, key, "s1", [ROWS[0]]) - bindir = pathlib.Path(td) / "bin" - bindir.mkdir() - capture = pathlib.Path(td) / "opened.txt" - code = bindir / "code" - code.write_text( - "#!/usr/bin/env bash\nprintf '%s' \"$1\" > \"$CC_OPEN_CAPTURE\"\n", - encoding="utf-8", - ) - code.chmod(code.stat().st_mode | stat.S_IXUSR) - env = dict(os.environ) - env["PATH"] = str(bindir) + os.pathsep + env["PATH"] - env["CC_OPEN_CAPTURE"] = str(capture) - rc = self.mod.main( - ["--open", "--cwd", str(cwd)], - config=str(config), - env=env, - ) - self.assertEqual(rc, 0) - self.assertEqual(capture.read_text(encoding="utf-8"), str(sess)) + def test_open_flag_is_removed(self): + # The "open the raw .jsonl in the editor" feature was dropped; --open must + # no longer be a recognized argument (argparse exits nonzero on unknown opt). + with self.assertRaises(SystemExit): + self.mod.main(["--open"], config="/nonexistent", env=dict(os.environ)) def test_fence_uses_longer_delimiter_when_body_has_backticks(self): # a tool/thinking payload that itself contains a ``` run must not close the From 13be700dd526589b522dcff1959ddcbb6153adb8 Mon Sep 17 00:00:00 2001 From: phase3dev <77866949+phase3dev@users.noreply.github.com> Date: Thu, 11 Jun 2026 05:00:17 -1000 Subject: [PATCH 3/7] feat(launcher): routing scrub + local-env anchor; docs: quick start, demote unmaintained thinking options Release-prep batch landing the hardened webview fixes plus launcher and docs work. - context-icon: match the guard pair by shape (variable-name captures) and store the matched names in the ownership marker, so patch and undo survive minified renames (t/c, Z/U) across bundles 2.1.108-2.1.172. - md-copy: keep visible thinking summaries copyable, gate tool-only turns so no stale/misplaced copy icon appears, and handle DETAILS/SUMMARY in the converter. - launcher: add CC_SCRUB_ROUTING (default off) to clear third-party model routing before launch, plus a `ccwa-local-env` injection anchor for personal builds; documented in the header toggle list. Covered by new bash + Windows tests. - docs: README Quick start with two downloads and three screenshots; demote the unmaintained extension.js patch and local proxy to short TECHNICAL.md links (kept as test-covered scripts); Compatibility refreshed through 2.1.172. 108 tests pass; gen-embeds --check --strict, bash -n, node --check, git diff --check clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 108 ++++++--------- TECHNICAL.md | 20 +-- fixes/context-icon/README.md | 21 +-- fixes/context-icon/fix-context-icon.py | 63 +++++++-- fixes/markdown-copy-export/README.md | 5 +- fixes/markdown-copy-export/add-md-copy.py | 2 +- fixes/markdown-copy-export/webview-inject.js | 40 ++++-- launcher/claudemax | 111 +++++++++++---- launcher/claudemax.win.js | 130 ++++++++++++++---- media/context-icon.png | Bin 0 -> 20304 bytes media/markdown.png | Bin 0 -> 63831 bytes media/thinking.png | Bin 0 -> 52122 bytes tests/test_md_converter.py | 9 ++ tests/test_md_inject.py | 70 ++++++++-- tests/test_md_patcher.py | 2 +- tests/test_reconcile.py | 72 ++++++++-- tests/test_regressions.py | 134 ++++++++++++++++++- 17 files changed, 606 insertions(+), 181 deletions(-) create mode 100644 media/context-icon.png create mode 100644 media/markdown.png create mode 100644 media/thinking.png diff --git a/README.md b/README.md index 8530f5f..14008d9 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,31 @@ Not affiliated with or endorsed by Anthropic. A future Claude Code update could Thinking summaries render empty in the VS Code extension and headless `-p`/SDK paths, even with `showThinkingSummaries` enabled. Fix via the launcher (recommended), a one-line extension patch, or a local proxy. -> [details](#workaround-1-thinking-summaries) + ![A populated Thinking summary in the VS Code chat instead of an empty block](media/thinking.png) + 2. **Missing context-usage icon (1M context window)** [updated 2026-06-08]. The context-usage pie in the chat input is hidden until you have used more than 50% of the context window. With the 1M window that is about 500,000 tokens, so it is effectively never shown. Fix via the launcher (re-patches the webview on each launch), or a standalone patcher script. -> [details](#workaround-2-context-usage-icon) + ![The context-usage pie icon and tooltip showing at 19 percent used, below the old 50 percent threshold](media/context-icon.png) + 3. **No markdown copy / export of chat** [added 2026-06-09]. The chat cannot copy a whole message or the whole conversation as Markdown, and has no transcript export. Fix via the launcher (adds copy controls, re-applied each launch), a standalone patcher, or a standalone session exporter CLI. -> [details](fixes/markdown-copy-export/README.md) + ![Per-message copy controls added to the VS Code chat](media/markdown.png) + +## Quick start + +Two downloads, one per platform: + +* **Linux / macOS:** [`launcher/claudemax`](launcher/claudemax) - the bash launcher, no build needed. +* **Windows:** `claudemax.exe` from the [Releases](../../releases) page. + +Put it on your PATH, point the VS Code "Claude Code" extension's `claudeCode.claudeProcessWrapper` setting at its full path, and reload the window. That enables every fix above. Terminal use, per-fix toggles, and the full wiring are under [The launcher](#the-launcher) and each workaround's section. + ## The launcher The recommended fix for everything is one small launcher that wraps the real `claude` binary. It is a drop-in process wrapper carrying every fix in this repo; each fix is on by default and independently switchable with an environment variable, so the same artifact serves "I want everything" and "I want only X" without editing code and without recompiling. @@ -72,13 +87,13 @@ Extended-thinking summaries stopped appearing with Opus 4.7 and remain unavailab * * -There are three workarounds: +There are technically three workarounds; only the launcher is maintained: -* **Option 1: Launcher (recommended).** A small wrapper that launches Claude Code and adds the missing flag. It fixes the VS Code extension and headless CLI, and it survives Claude Code updates. -* **Option 2: One-line patch.** A direct edit to one line of the VS Code extension. This only fixes VS Code and must be re-applied after each extension update. -* **Option 3: Local proxy (advanced).** A localhost proxy that can fix all surfaces at once. This is powerful but untested and is documented for users who want to evaluate it. +1. **Launcher (recommended).** A small wrapper that launches Claude Code and adds the missing flag. It fixes the VS Code extension and headless CLI, and it survives Claude Code updates. +2. **One-line `extension.js` patch.** A direct edit to one line of the VS Code extension; see [TECHNICAL.md](TECHNICAL.md#option-2-extensionjs-patch). VS Code only, and must be re-applied after each extension update. Maintained through `2.1.172`; not maintained after. +3. **Local proxy (advanced).** A localhost proxy that fixes all surfaces at the wire level; see [TECHNICAL.md](TECHNICAL.md#option-3-local-proxy-design). Powerful but untested. Not maintained. -## Option 1: Launcher (recommended) +## Launcher The launcher starts the real `claude` binary and appends the missing `--thinking-display summarized` flag. It does not modify Claude Code files, so it continues working after updates. The same wrapper fixes both the VS Code extension and headless CLI. @@ -139,7 +154,7 @@ The same result is achieved with the compiled `.exe`. ### Want only the thinking fix? -Set `CC_PATCH_CONTEXT_ICON=0` to leave the webview untouched and inject only the thinking-display flag. The launcher reads `CC_THINKING_DISPLAY`: +Set `CC_PATCH_CONTEXT_ICON=0` and `CC_PATCH_MD_COPY=0` to leave the webview untouched and inject only the thinking-display flag. The launcher reads `CC_THINKING_DISPLAY`: * unset or `summarized`: show thinking summaries, which is the default * `omitted`: hide thinking summaries @@ -152,7 +167,7 @@ When Claude Code starts a real agent run it puts one of these markers on the com The launcher inspects the arguments and, when it detects a real run via any of those markers, appends `--thinking-display summarized` before handing off to the real `claude` binary. The official extension also launches the wrapper with the real CLI path as a leading argument (a "process wrapper" convention); the launcher detects and consumes that path so it is not forwarded as a stray positional. See [TECHNICAL.md](TECHNICAL.md) for more. -### Why Option 1 is recommended +### Why the launcher is recommended 1. It survives updates because it does not edit Claude Code files (for the thinking fix; the context-icon fix re-applies on each launch). 2. It fixes both the VS Code extension and headless `claude -p` or SDK runs. @@ -162,56 +177,14 @@ The launcher inspects the arguments and, when it detects a real run via any of t 6. Every fix can be toggled with one environment variable. 7. It provides one place to configure effort level, auto mode, timeouts, or model routing. See the commented customization section in the script and the [Side note](#side-note-launching-claude-code-with-third-party-models). -## Option 2: One-line `extension.js` patch (VS Code only) - -If you only use the VS Code extension and accept reapplying the change after updates, you can patch the extension directly. The fix is a single line: - -```js -// from: -if(l.type!=="disabled"&&l.display)B.push("--thinking-display",l.display) -// to: -if(l.type!=="disabled")B.push("--thinking-display",l.display||"summarized") -``` - -> The extension is minified, so the array variable name varies by build (`B` in 2.0.x, `q` in 2.1.16x, and so on). Match the surrounding text and keep whatever variable name your build uses; [`fixes/thinking-summaries/patch-extension.sh`](fixes/thinking-summaries/patch-extension.sh) does this automatically. This version fragility is one reason Option 1 is preferred. - -### Automatic patching on Linux, macOS, WSL, or Git Bash - -[`fixes/thinking-summaries/patch-extension.sh`](fixes/thinking-summaries/patch-extension.sh) finds every installed Claude Code extension, backs each one up, and applies the patch: +## Other approaches (not maintained) -```sh -./fixes/thinking-summaries/patch-extension.sh # patch and create .bak backups -./fixes/thinking-summaries/patch-extension.sh --dry-run # preview only, change nothing -./fixes/thinking-summaries/patch-extension.sh --revert # restore backups -``` - -Reload the VS Code window after patching. Re-run the patch after every extension update because updates replace the extension folder and remove the change. - -### Manual patching on any OS - -Find the extension's `extension.js` file, back it up, replace the line shown above, save the file, and reload VS Code. Common locations: - -* Linux/macOS: `~/.vscode/extensions/anthropic.claude-code-*/` -* Windows: `%USERPROFILE%\.vscode\extensions\anthropic.claude-code-*\` - -### Why Option 1 is preferred over Option 2 - -1. Option 2 must be re-applied after every extension update. -2. Option 2 fixes VS Code only. Headless `claude -p` and SDK runs still come back empty. - -## Option 3: Local proxy (advanced, untested) - -A localhost proxy can add the missing field to every request, fixing VS Code, CLI, and SDK runs without editing files or reapplying patches. This works because each surface honors `ANTHROPIC_BASE_URL`. - -This is a working starting point, not a turnkey fix. It also sits in the path of your live auth token, so review the security notes before relying on it. - -```sh -node fixes/thinking-summaries/proxy.js # listens on http://127.0.0.1:8788 -export ANTHROPIC_BASE_URL=http://127.0.0.1:8788 # set this where Claude launches -claude ... # for VS Code, set it for the extension host, then reload -``` +The one-line `extension.js` patch and the local proxy are documented in full - +with their trade-offs and the runnable scripts - in TECHNICAL.md. Neither is +maintained; the launcher is the supported fix. -Security: The proxy sees your live auth token. It binds to `127.0.0.1` only, never `0.0.0.0`, and does not log headers or bodies. Unset `ANTHROPIC_BASE_URL` to return directly to Anthropic. See [`fixes/thinking-summaries/proxy.js`](fixes/thinking-summaries/proxy.js) and [TECHNICAL.md](TECHNICAL.md#option-3-local-proxy-design) for details and caveats. +* [Option 2: extension.js patch](TECHNICAL.md#option-2-extensionjs-patch) - VS Code only, and must be re-applied after every extension update. Script: [`fixes/thinking-summaries/patch-extension.sh`](fixes/thinking-summaries/patch-extension.sh) (`--revert`, `--dry-run`). Maintained through `2.1.172`; not maintained after. +* [Option 3: local proxy](TECHNICAL.md#option-3-local-proxy-design) - surface-agnostic (VS Code + CLI + SDK) but untested, and it sits in the path of your live auth token. Script: [`fixes/thinking-summaries/proxy.js`](fixes/thinking-summaries/proxy.js). Not maintained. --- @@ -232,19 +205,19 @@ There is no environment variable or CLI flag for this threshold, so the fix is a The launcher carries this fix on by default. Install and wire it up exactly like Workaround 1 (copy [`launcher/claudemax`](launcher/claudemax) to `~/.local/bin`, point `claudeCode.claudeProcessWrapper` at it, reload). On Windows, download `claudemax.exe` from [Releases](../../releases). To get only the context-icon fix and skip thinking injection, set `CC_THINKING_DISPLAY=omitted`. -On each launch the wrapper reconciles the extension's `webview/index.js`, flipping the hidden threshold so the icon shows at any usage level. Because it re-applies every launch, an extension auto-update that reinstalls a fresh bundle is re-patched on the next launch. +On each launch the wrapper reconciles the extension's `webview/index.js`, removing the startup hide guard and flipping the hidden threshold so the icon shows at any usage level. Because it re-applies every launch, an extension auto-update that reinstalls a fresh bundle is re-patched on the next launch. > First-run note: the wrapper patches `index.js` on disk when the CLI is spawned, which can be **after** the webview already loaded the old bundle. The first time you enable it you may need **two reloads**: reload once (the spawn patches the file), then reload again (the webview loads the patched bundle). Later windows and post-update launches are already patched on disk. ### What it changes -In the indicator component, the render gate is `if (c >= 50) return null`, where `c` is the percent of context **remaining**. So the icon renders only when less than 50% remains (more than 50% used). The fix flips the threshold and tags the edit with an ownership marker: +In the indicator component, the render gates are `if (t === 0) return null` and `if (c >= 50) return null`, where `t` is the known context window and `c` is the percent of context **remaining**. So the icon renders only after the webview knows a session and less than 50% remains (more than 50% used). The fix removes the startup guard, flips the threshold, and tags the edit with an ownership marker: ```text -if(c>=50)return null -> if(c>=101)return null}/*ccwa-context-icon*/ +if(t===0)return null;if(c>=50)return null} -> if(c>=101)return null}/*ccwa-context-icon:t:c*/ ``` -`c` maxes at 100, so `c >= 101` is never true and the gate never hides the icon. The separate `if (t === 0) return null` guard (no context window known yet) is left intact, so nothing renders before a session exists. The edit is anchored on the stable string `>=50)return null}`, not on the minified component name, which changes between builds. The trailing `/*ccwa-context-icon*/` marks the edit as ours, so the launcher only ever reverses its own change. +`c` maxes at 100, so `c >= 101` is never true and the gate never hides the icon. Removing `if (t === 0) return null` keeps the icon visible after a window reload while usage data is still being repopulated; during that gap it can briefly show `0%`. The edit is anchored on the minified guard-pair shape above, not on the component name or exact minified variable names, which change between builds. The trailing `/*ccwa-context-icon::*/` marker stores the matched names so the launcher can reverse only its own change back to the same pristine variable names. ### Turn the context-icon fix on or off @@ -257,8 +230,8 @@ The launcher reads `CC_PATCH_CONTEXT_ICON`: Unlike Workaround 1, this fix edits the extension's bundled `webview/index.js`. The edit is made safe: -* **Idempotent** - it skips a file that is already in the desired state, and skips (rather than guesses) if the `>=50)return null}` anchor is absent because the extension changed. -* **Ownership-marked** - the edit carries a `/*ccwa-context-icon*/` marker; the launcher reverses only its own marked edit and never touches upstream code that merely resembles a patched value. +* **Idempotent** - it skips a file that is already in the desired state, and skips (rather than guesses) if the combined guard shape is absent because the extension changed. +* **Ownership-marked** - the edit carries a `/*ccwa-context-icon::*/` marker; the launcher reverses only its own marked edit and never touches upstream code that merely resembles a patched value. Older `/*ccwa-context-icon*/` markers from prior versions are still recognized and normalized. * **Snapshotted once** - a whole-file pristine snapshot `index.js.bak-cc-workarounds` is written the first time the file is rewritten, for emergency manual restore only; routine reconcile never reads it. * **Atomic** - the change is written to a temp file and moved into place only after it is verified, so a failed or partial write leaves the original untouched. * **Best-effort** - every step is guarded; a read-only file, a renamed bundle, or a missing tool simply no-ops and never blocks the launch. @@ -279,7 +252,7 @@ After patching, reload the webview (Command Palette -> "Developer: Reload Window ## Known limitations 1. **Coarse glyph.** The pie is a 3-state gauge, not a continuous fill: it only changes appearance at roughly 62.5% and 87% used. The precise percentage is in the hover tooltip and in `/context`, not in the glyph itself. Making the pie a fine-grained gauge would require new SVG geometry, not a one-line patch, so it is out of scope. -2. **Transient 0% right after a reload.** The icon reads from a usage store that resets to zero on a window reload and is repopulated by the next assistant turn. Immediately after reloading a continued conversation, before any new turn, the tooltip can briefly read "0% context used"; it self-corrects to the true value after the next turn. (`/context` is unaffected - it queries the CLI directly.) If you would rather hide the icon while the store is empty than show a transient 0%, change the icon's `if(t===0)return null` to `if(t<=0)return null` in `webview/index.js`; the icon then stays hidden until the first turn populates real numbers. This is a manual, optional tweak and is not applied by default. +2. **Transient 0% right after a reload.** The icon reads from a usage store that resets to zero on a window reload and is repopulated by the next assistant turn. Immediately after reloading a continued conversation, before any new turn, the tooltip can briefly read "0% context used"; it self-corrects to the true value after the next turn. (`/context` is unaffected - it queries the CLI directly.) This is intentional: showing a temporary 0% icon is preferable to hiding the icon for the whole reload gap. --- @@ -317,8 +290,8 @@ Setup is otherwise identical to Option 1. This is unrelated to the fixes above. | [`launcher/claudemax`](launcher/claudemax) | both | Unified launcher (Linux/macOS), env-toggled. | | [`launcher/claudemax.win.js`](launcher/claudemax.win.js) | both | Windows source for `claudemax.exe`. | | [`launcher/README.md`](launcher/README.md) | both | Wiring, the toggle table, the VS Code env-setting how-to, the build command. | -| [`fixes/thinking-summaries/patch-extension.sh`](fixes/thinking-summaries/patch-extension.sh) | thinking | Option 2 idempotent `extension.js` patch with `--revert` and `--dry-run`. | -| [`fixes/thinking-summaries/proxy.js`](fixes/thinking-summaries/proxy.js) | thinking | Option 3 localhost proxy. Advanced and untested. | +| [`fixes/thinking-summaries/patch-extension.sh`](fixes/thinking-summaries/patch-extension.sh) | thinking | Option 2 `extension.js` patch (`--revert`, `--dry-run`). Unmaintained; see TECHNICAL.md. | +| [`fixes/thinking-summaries/proxy.js`](fixes/thinking-summaries/proxy.js) | thinking | Option 3 localhost proxy. Advanced, untested, unmaintained. | | [`fixes/thinking-summaries/test-thinking-display.sh`](fixes/thinking-summaries/test-thinking-display.sh) | thinking | Live A/B test showing that the flag is the relevant lever. | | [`fixes/context-icon/fix-context-icon.py`](fixes/context-icon/fix-context-icon.py) | context icon | Option 2 standalone webview patcher with `--revert`. | | [`fixes/markdown-copy-export/add-md-copy.py`](fixes/markdown-copy-export/add-md-copy.py) | markdown copy | Standalone webview patcher (sentinel block, reverse-transform `--revert`). | @@ -342,9 +315,10 @@ pkg launcher/claudemax.win.js --targets node18-win-x64 --output claudemax.exe ## Compatibility -Confirmed on Opus 4.7 and Opus 4.8 with VS Code extension `2.1.169` (native-binary CLI), via the `claudeCode.claudeProcessWrapper` setting, on Windows 11 and Ubuntu 24.04. +Confirmed on Opus 4.7 and Opus 4.8 with VS Code extension builds `2.1.169` through `2.1.172` (native-binary CLI), via the `claudeCode.claudeProcessWrapper` setting, on Windows 11 and Ubuntu 24.04. -* **Thinking fix:** earlier builds (`2.1.165` / `2.1.167`) signaled thinking with `--thinking adaptive`; `2.1.169` uses `--max-thinking-tokens` on the VS Code path. The launcher keys off either, plus `-p`/`--print` for headless. The `--thinking-display` flag and the request field are stable levers; the Option 2 minified strings can change between releases (the script matches generically and skips if not found). -* **Context-icon fix:** the `>50% used` gate appeared around `2.1.165` (absent in `2.1.131` / `2.1.128`). The patch anchors on the stable substring `>=50)return null}`; if a future build changes that exact string, the launcher safely no-ops and the anchor needs updating. +* **Thinking fix:** earlier builds (`2.1.165` / `2.1.167`) signaled thinking with `--thinking adaptive`; `2.1.169` and later use `--max-thinking-tokens` on the VS Code path. The launcher keys off either, plus `-p`/`--print` for headless. The `--thinking-display` flag and the request field are stable levers; the Option 2 minified strings can change between releases (the script matches generically and skips if not found). +* **Context-icon fix:** the `>50% used` gate has been observed with different minified names (`Z/U` in `2.1.108` and `2.1.131`, `t/c` in `2.1.170` and `2.1.172`). The patch matches the guard-pair shape with variable-name captures and records the matched names in its ownership marker, so it patches and reverts cleanly regardless of the names; if a future build changes the guard shape itself, the launcher safely no-ops and the anchor needs updating. +* **Markdown copy/export fix:** the copy controls are appended to the webview bundle and re-applied each launch. The injection is anchored on stable structural markers and skips cleanly if the bundle shape changes. Behavior may change in future Claude Code releases. diff --git a/TECHNICAL.md b/TECHNICAL.md index 2be784d..a6d904a 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -96,6 +96,8 @@ The launcher intentionally sets no environment of its own by default; it only ad ### Option 2: extension.js patch +> **Unmaintained.** Documented for reference. VS Code only, and must be re-applied after every extension update; the launcher (Option 1) is the supported fix. + A one-line change so the non-interactive spawn always forwards the flag: ```js @@ -113,6 +115,8 @@ Toggle idea (untested): changing the line to `l.display || (process.env.CC_THINK ### Option 3: local proxy (design) +> **Unmaintained and untested.** A design and a working starting point, not a turnkey fix; it sits in the path of your live auth token. The launcher (Option 1) is the supported fix. + The most thorough fix: a small localhost forward proxy that injects the field at the wire level, so it is surface-agnostic (VS Code + CLI + SDK), needs no install edits, and survives updates. It works because every Claude Code surface resolves the API host as: ``` @@ -172,17 +176,17 @@ function FJe({usedTokens:e, contextWindow:t, onCompact:i, buttonClassName:n}) { } ``` -`c` is the percent of context *remaining*, so `c >= 50` hides the icon whenever at least half the window is free, i.e. it only appears once more than 50% is used. With a 1M window that is 500k tokens. Older bundles (`2.1.131`, `2.1.128`) do not contain this gate; it appeared around `2.1.165`, which matches users' recollection that the icon used to be visible. +`c` is the percent of context *remaining*, so `c >= 50` hides the icon whenever at least half the window is free, i.e. it only appears once more than 50% is used. With a 1M window that is 500k tokens. The same guard shape is present in the locally inspected `2.1.131` (`Z/U`) and `2.1.170` (`t/c`) bundles, with different minified names. The `CLAUDE_CODE_DISABLE_1M_CONTEXT=1` env var that circulates in the issue threads only shrinks the window to 200k so 50% (100k) is reached sooner. It does not touch the threshold and forces giving up the 1M window, so it is not a real fix. ## The fix ```text -if(c>=50)return null -> if(c>=101)return null}/*ccwa-context-icon*/ +if(t===0)return null;if(c>=50)return null} -> if(c>=101)return null}/*ccwa-context-icon:t:c*/ ``` -`c` is in `[0, 100]`, so `c >= 101` is never true and the gate never hides the icon. The separate `if (t === 0) return null` guard is left intact, so nothing renders before a context window is known. Using `>=101` (rather than deleting the line) is the smallest, most legible, greppable, reversible change, and it preserves the surrounding structure for a clean string substitution. The patch is anchored on the literal `>=50)return null}`, which is stable across builds even though the minified names around it are not, and which occurs exactly once in `2.1.169`. The trailing `/*ccwa-context-icon*/` is an ownership marker: the launcher's reconcile reverses only the marked form, so it can never corrupt upstream code that merely matches a patched value, and a pre-existing unmarked patch from an older launcher is left as-is and re-marked on the next fresh bundle. +`c` is in `[0, 100]`, so `c >= 101` is never true and the gate never hides the icon. Removing `if (t === 0) return null` keeps the icon visible across window-reload gaps where the webview has not repopulated usage data yet; the tooltip can briefly read `0%` until the next usage event arrives. Using `>=101` (rather than deleting the line) is the smallest, most legible, greppable, reversible threshold change, while removing the adjacent startup guard addresses the reload case explicitly. The patch is anchored on the combined guard shape `if(===0)return null;if(>=50)return null}`, not the exact minified names. The trailing `/*ccwa-context-icon::*/` is an ownership marker that stores the matched names: the launcher's reconcile reverses only known patch fingerprints, so it can never corrupt unrelated upstream code; pre-existing legacy patched forms are normalized to pristine and re-applied on the next launch. There is no integrity or subresource check on the webview bundle (the only `sha256` references in `extension.js` belong to a bundled crypto library), so an edited `index.js` loads normally. @@ -197,8 +201,8 @@ The wrapper discovers `index.js` two ways: The edit is made safe: -* Idempotent: writes only when the recomputed bytes differ, and skips (rather than guesses) if the `>=50)return null}` anchor is absent because the extension changed. -* Ownership-marked: the edit carries a `/*ccwa-context-icon*/` marker; reconcile reverses only its own marked edit and never touches upstream code that merely resembles a patched value. +* Idempotent: writes only when the recomputed bytes differ, and skips (rather than guesses) if the combined guard shape is absent because the extension changed. +* Ownership-marked: the edit carries a `/*ccwa-context-icon::*/` marker; reconcile reverses only its own marked edit and never touches upstream code that merely resembles a patched value. Older `/*ccwa-context-icon*/` forms are treated as legacy fingerprints. * Atomic: written to a temp file and moved into place only after it is verified non-empty and actually patched, so a failed or partial write cannot corrupt the bundle. * Metadata-preserving via `cp -p` (portable; the GNU-only `chmod`/`chown --reference` is avoided so it also works on macOS/BSD). The Windows launcher writes with `fs.writeFileSync` + `fs.renameSync`, inheriting the parent directory's ACLs (it does not preserve the file mode - the one intentional bash/node asymmetry). * Fully guarded so it never blocks the launch (a read-only file, a renamed bundle, or a missing tool simply no-ops). @@ -251,9 +255,9 @@ React.createElement(FJe, { }); ``` -`usageData` initializes to all zeros and is only filled by usage events that arrive during an assistant turn. There is no seeding from `get_claude_state` on resume, so immediately after a window reload of a continued conversation, before any new turn, the store is still `{0,0,0,0}`: `usedTokens = 0` (the tooltip reads "0% used") and `contextWindow = 0 - 0 - 13000 = -13000` (negative, so the `t === 0` guard does not fire and, after the patch, the icon still renders at 0%). This self-corrects on the next turn, when a usage event fills the store with the real totals. `/context` does not use this store; it queries the CLI directly, so it always shows the true number. +`usageData` initializes to all zeros and is only filled by usage events that arrive during an assistant turn. There is no seeding from `get_claude_state` on resume, so immediately after a window reload of a continued conversation, before any new turn, the store is still empty: the tooltip can read "0% used" until a usage event fills the store with the real totals. The patch removes the `t === 0` hide guard so this state still renders an icon instead of hiding it for the whole reload gap. `/context` does not use this store; it queries the CLI directly, so it always shows the true number. -If showing a transient 0% is undesirable, changing `if(t===0)return null` to `if(t<=0)return null` hides the icon while the store is empty (`t` is negative then) and shows it with the correct value after the first turn. This is documented as an optional manual tweak and is not applied by default. +The transient `0%` is deliberate: it is the same stale-data symptom the extension can already show, and it is less confusing than no icon at all after a reload. ### The glyph is a coarse 3-state gauge @@ -269,4 +273,4 @@ The pie button's `onClick` is `onCompact`: clicking the icon triggers compaction ## Compatibility -Confirmed on VS Code extension `2.1.169` (native-binary CLI) on Windows 11 and Ubuntu 24.04. The `>50% used` gate appeared around `2.1.165` (absent in `2.1.131` / `2.1.128`). The patch keys off the stable substring `>=50)return null}`, not the minified component name; if a future build changes that exact substring, the launcher safely no-ops (the icon goes missing again) until the anchor is updated. The standalone [`fix-context-icon.py`](fixes/context-icon/fix-context-icon.py) applies the same change directly and supports `--revert`. +Confirmed on VS Code extension `2.1.169` (native-binary CLI) on Windows 11 and Ubuntu 24.04. The same guard shape has been observed with different minified names (`Z/U` in `2.1.131`, `t/c` in `2.1.170`). The patch keys off that shape, not the minified component name or exact variable names; if a future build changes the shape, the launcher safely no-ops (the icon goes missing again) until the anchor is updated. The standalone [`fix-context-icon.py`](fixes/context-icon/fix-context-icon.py) applies the same change directly and supports `--revert`. diff --git a/fixes/context-icon/README.md b/fixes/context-icon/README.md index ad27254..e1015b1 100644 --- a/fixes/context-icon/README.md +++ b/fixes/context-icon/README.md @@ -5,8 +5,9 @@ Restores the always-visible context-usage icon in the VS Code chat input. Extension builds 2.1.165+ hide that icon until you have used more than 50% of the context window. With the 1M context window that is ~500k tokens, so it is -effectively never shown. This fix flips the threshold so the icon renders -whenever a context window is known, at any usage level. +effectively never shown. This fix removes the startup hide guard and flips the +threshold so the icon renders at any usage level, including the reload gap before +fresh usage data arrives. ## Standalone usage @@ -29,12 +30,16 @@ launcher reverts our edit on the next launch. ## Maintenance Contract -- Anchors / selectors: `>=50)return null}` (component `FJe` in `webview/index.js`). -- Ownership marker: `/*ccwa-context-icon*/`. Apply rewrites `>=50)return null}` -> - `>=101)return null}/*ccwa-context-icon*/`; undo reverses only that marked form. -- Failure mode if an anchor moves: if the anchor string changes, apply no-ops with - a one-line warning (the icon goes missing again) until the anchor is updated. A - bare upstream `>=101)return null}` with no marker is never touched. +- Anchors / selectors: `if(===0)return null;if(>=50)return null}` + (component `FJe` in `webview/index.js`). The identifier names are captured, not + hardcoded. +- Ownership marker: `/*ccwa-context-icon::*/`. Apply + rewrites the combined guard to `if(>=101)return null}`; + undo uses the marker metadata to restore the pristine combined guard with the + same variable names. Older `/*ccwa-context-icon*/` markers are recognized as + legacy fingerprints. +- Failure mode if an anchor moves: if the guard shape changes, apply no-ops with a + one-line warning (the icon goes missing again) until the anchor is updated. - Launcher registry entry: feature id `context-icon`, file `webview/index.js`, apply = marked swap, undo = reverse of the marked form only. - Test fixture: `tests/test_reconcile.py` (launcher engine, both platforms) and diff --git a/fixes/context-icon/fix-context-icon.py b/fixes/context-icon/fix-context-icon.py index 6191517..a72ad1c 100755 --- a/fixes/context-icon/fix-context-icon.py +++ b/fixes/context-icon/fix-context-icon.py @@ -17,14 +17,20 @@ } With the 1M context window, 50% used = 500,000 tokens, so the icon stays hidden -for virtually an entire normal session. This script flips the threshold so the -icon is visible whenever a context window is known (t>0), at any usage level. +for virtually an entire normal session. This script removes the startup guard and +flips the threshold so the icon stays visible across reload gaps and then +self-corrects when fresh usage data arrives. - if(c>=50)return null -> if(c>=101)return null}/*ccwa-context-icon*/ (marked; c maxes at 100) + if(t===0)return null;if(c>=50)return null} + -> if(c>=101)return null}/*ccwa-context-icon:t:c*/ (marked; c maxes at 100) -The (t===0) guard is left intact. In a resumed window, the webview can still show -a transient 0% before the first fresh response updates context metadata. After -that first response the icon stays visible. +The minified variable names are not stable across builds; the patcher matches the +guard shape with any ASCII JS identifier pair and stores the matched names in the +marker so undo can restore the same pristine names. + +In a resumed window, the webview can still show a transient 0% before the first +fresh response updates context metadata. After that first response the icon +corrects. SAFE & REVERSIBLE ----------------- @@ -48,14 +54,45 @@ """ import glob import os +import re import shutil import sys import tempfile -OLD = ">=50)return null}" -NEW = ">=101)return null}/*ccwa-context-icon*/" +IDENT = r"[A-Za-z_$][A-Za-z0-9_$]*" +OLD_RE = re.compile(rf"if\(({IDENT})===0\)return null;if\(({IDENT})>=50\)return null\}}") +MARKED_RE = re.compile( + rf"if\(({IDENT})>=101\)return null\}}/\*ccwa-context-icon:({IDENT}):\1\*/" +) +OLD = "if(t===0)return null;if(c>=50)return null}" +NEW_BARE = "if(c>=101)return null}" +NEW_LEGACY = NEW_BARE + "/*ccwa-context-icon*/" +NEW = "if(c>=101)return null}/*ccwa-context-icon:t:c*/" +LEGACY_BARE = "if(t===0)return null;if(c>=101)return null}" +LEGACY_NEW = LEGACY_BARE + "/*ccwa-context-icon*/" BACKUP_SUFFIX = ".bak-context-icon" + +def old_guard(first_var, remaining_var): + return f"if({first_var}===0)return null;if({remaining_var}>=50)return null}}" + + +def marked_guard(first_var, remaining_var): + return ( + f"if({remaining_var}>=101)return null}}" + f"/*ccwa-context-icon:{first_var}:{remaining_var}*/" + ) + + +def undo_known_patches(data): + data = MARKED_RE.sub(lambda m: old_guard(m.group(2), m.group(1)), data) + return ( + data.replace(LEGACY_NEW, OLD) + .replace(NEW_LEGACY, OLD) + .replace(LEGACY_BARE, OLD) + .replace(NEW_BARE, OLD) + ) + DISCOVERY_GLOBS = [ os.path.expanduser("~/.vscode/extensions/anthropic.claude-code-*/webview/index.js"), os.path.expanduser("~/.vscode-server/extensions/anthropic.claude-code-*/webview/index.js"), @@ -103,18 +140,22 @@ def write_atomic_preserving_metadata(path, text): def patch_file(path): with open(path, "r", encoding="utf-8", newline="") as f: data = f.read() - if NEW in data: + if MARKED_RE.search(data): return "already-patched" - n = data.count(OLD) + data = undo_known_patches(data) + matches = list(OLD_RE.finditer(data)) + n = len(matches) if n == 0: return "gate-not-found (extension version changed? re-inspect FJe in index.js)" if n > 1: return f"ambiguous ({n} matches) — skipped for safety" + match = matches[0] + patched = data[: match.start()] + marked_guard(match.group(1), match.group(2)) + data[match.end() :] backup = path + BACKUP_SUFFIX if not os.path.exists(backup): with open(backup, "w", encoding="utf-8", newline="") as b: b.write(data) - write_atomic_preserving_metadata(path, data.replace(OLD, NEW, 1)) + write_atomic_preserving_metadata(path, patched) return "PATCHED" diff --git a/fixes/markdown-copy-export/README.md b/fixes/markdown-copy-export/README.md index 29ff44e..9ea4116 100644 --- a/fixes/markdown-copy-export/README.md +++ b/fixes/markdown-copy-export/README.md @@ -39,8 +39,9 @@ controls and reverts ours on the next launch. `CC_WORKAROUNDS=0` reverts it too. - Anchors / selectors: user bubble `[class*="userMessageContainer_"]`; assistant bubble `[data-testid="assistant-message"]` (NOT `[data-message-rating]` — that is the nested, experiment+analytics-gated rating widget, which the sanitizer strips); - chrome strip-prefixes `toolUse_`/`toolResult_`/`toolReference_`/`thinking_`/ - `unknownContent_` plus `[data-message-rating]` and `button`; + chrome strip-prefixes `toolUse_`/`toolResult_`/`toolReference_`/ + `unknownContent_` plus `[data-message-rating]` and `button`; visible + `thinking_` summaries are content and remain copyable; clipboard write via a synchronous `document.execCommand("copy")` first (gesture-safe and works without a secure context, e.g. remote / code-server), falling back to `navigator.clipboard.writeText`; the icon only flips to a diff --git a/fixes/markdown-copy-export/add-md-copy.py b/fixes/markdown-copy-export/add-md-copy.py index 403a8f2..50c4290 100644 --- a/fixes/markdown-copy-export/add-md-copy.py +++ b/fixes/markdown-copy-export/add-md-copy.py @@ -31,7 +31,7 @@ BACKUP_SUFFIX = ".bak-md-copy" # >>>CCWA-MD-COPY-EMBED>>> (generated by tools/gen-embeds; do not edit) -INJECT_JS = base64.b64decode("LyogY2MtbWQtY29weTogcGVyLW1lc3NhZ2UgYW5kIHdob2xlLWNvbnZlcnNhdGlvbiBjb3B5IChNYXJrZG93bikgZm9yIHRoZQogKiBDbGF1ZGUgQ29kZSBWUyBDb2RlIHdlYnZpZXcuIFNlbGYtY29udGFpbmVkIElJRkUgYXBwZW5kZWQgdG8gd2Vidmlldy9pbmRleC5qcy4KICogRWFjaCBjb250cm9sIGlzIGEgc2luZ2xlIGNsaXBib2FyZCBpY29uIHRoYXQgZmxpcHMgdG8gYSBjaGVja21hcmsgZm9yIH4ycyB3aGVuIGEKICogY29weSBhY3R1YWxseSBzdWNjZWVkcyAobm8gdGV4dCBsYWJlbCwgbm8gbWVudSkuIEFkZGl0aXZlIGFuZCByZWFkLW9ubHkgdy5yLnQuCiAqIGFwcCBzdGF0ZTsga2V5ZWQgb24gc3RhYmxlIENTUy1tb2R1bGUgY2xhc3MgcHJlZml4ZXMsIHNvIGl0IGZhaWxzIHNhZmUgKGNvbnRyb2xzCiAqIHNpbXBseSBkbyBub3QgYXBwZWFyKSBpZiBhIHByZWZpeCBtb3Zlcy4KICogRXhwb3NlcyBpdHMgcHVyZSBmdW5jdGlvbnMgZm9yIG5vZGUgdW5pdCB0ZXN0czsgYm9vdCgpcyBvbmx5IGluIGEgcmVhbCB3ZWJ2aWV3LiAqLwovKiBMZWFkaW5nICc7JyBzbyB0aGF0LCBhcHBlbmRlZCBhZnRlciB0aGUgYnVuZGxlLCB0aGlzIElJRkUgY2FuIG5ldmVyIGJlIHBhcnNlZCBhcwogKiBhIGNhbGwgb24gdGhlIGJ1bmRsZSdzIGZpbmFsIGV4cHJlc3Npb24gaWYgaXQgbGFja3MgYSB0cmFpbGluZyBzZW1pY29sb24gKEFTSQogKiBzYWZldHkgYWNyb3NzIGV4dGVuc2lvbiBidWlsZHMpLiAqLwo7KGZ1bmN0aW9uICgpIHsKICAidXNlIHN0cmljdCI7CgogIHZhciBDT05UUk9MX1BSRUZJWCA9ICJjYy1tZC1jb3B5IjsgLy8gZXZlcnkgaW5qZWN0ZWQgbm9kZSdzIGNsYXNzIHN0YXJ0cyB3aXRoIHRoaXMKICB2YXIgVVNFUl9CVUJCTEUgPSAnW2NsYXNzKj0idXNlck1lc3NhZ2VDb250YWluZXJfIl0nOwogIC8vIEFzc2lzdGFudCBtZXNzYWdlIHdyYXBwZXIuIFZlcmlmaWVkIG9uIDIuMS4xNzA6IHRoZSByZW5kZXIgZW1pdHMgZXhhY3RseSBvbmUKICAvLyBgZGF0YS10ZXN0aWQ9ImFzc2lzdGFudC1tZXNzYWdlImAgZGl2IHBlciBhc3Npc3RhbnQgdHVybiwgd2l0aCB0aGUgcmF0aW5nCiAgLy8gd2lkZ2V0IGFuZCBjb250ZW50IGJsb2NrcyBhcyBpdHMgY2hpbGRyZW4uIChUaGUgZWFybGllciBgW2RhdGEtbWVzc2FnZS1yYXRpbmddYAogIC8vIHdhcyBXUk9ORzogdGhhdCBhdHRyaWJ1dGUgc2l0cyBvbiB0aGUgbmVzdGVkIHJhdGluZyBjb250cm9sLCB3aGljaCBpcyBhbHNvIG9ubHkKICAvLyByZW5kZXJlZCBiZWhpbmQgYW4gZXhwZXJpbWVudCthbmFseXRpY3MgZ2F0ZS4pIFJlLXBpbm5lZCBpbiBUYXNrIDYuCiAgdmFyIEFTU0lTVEFOVF9CVUJCTEUgPSAnW2RhdGEtdGVzdGlkPSJhc3Npc3RhbnQtbWVzc2FnZSJdJzsKICB2YXIgTUVTU0FHRVNfQ09OVEFJTkVSID0gJ1tjbGFzcyo9Im1lc3NhZ2VzQ29udGFpbmVyXyJdJzsgLy8gZS5nLiAnW2NsYXNzKj0idGltZWxpbmVfIl0nOyAiIiAtPiBvYnNlcnZlIGRvY3VtZW50LmJvZHkKICAvLyBPcHRpb25hbCBuYXJyb3dpbmcgb25seS4gTVVTVCBiZSBhIHNpbmdsZSB3cmFwcGVyIGFyb3VuZCBBTEwgY29udGVudCBibG9ja3MsCiAgLy8gbm90IGEgcGVyLWJsb2NrIGNsYXNzIChhIHR1cm4gaGFzIG11bHRpcGxlIGJsb2NrcykuICIiIC0+IHVzZSB0aGUgYnViYmxlIGl0c2VsZgogIC8vIChhbHJlYWR5IGFnZ3JlZ2F0ZXMgYWxsIGJsb2Nrczsgc2FuaXRpemVDbG9uZSBpcyB0aGUgY29ycmVjdG5lc3MgZ2F0ZSkuCiAgdmFyIEFTU0lTVEFOVF9DT05URU5UID0gIiI7CiAgdmFyIEZFRURCQUNLX01TID0gMjAwMDsgLy8gaG93IGxvbmcgdGhlIGNoZWNrbWFyayBzaG93cyBhZnRlciBhIHN1Y2Nlc3NmdWwgY29weQoKICAvLyAtLS0tIEhUTUwgLT4gTWFya2Rvd24gKERPTSB3YWxrKSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgLy8gVXNlcyBvbmx5OiBub2RlVHlwZSwgdGFnTmFtZSwgY2hpbGROb2RlcywgdGV4dENvbnRlbnQsIGdldEF0dHJpYnV0ZSwgY2xhc3NOYW1lLgogIGZ1bmN0aW9uIGh0bWxUb01hcmtkb3duKHJvb3QpIHsKICAgIC8vIExvbmdlc3QgcnVuIG9mIGNvbnNlY3V0aXZlIGJhY2t0aWNrcyBpbiBzLCBzbyBhIGNvZGUgZGVsaW1pdGVyL2ZlbmNlIGNhbiBiZQogICAgLy8gY2hvc2VuIGxvbmdlciB0aGFuIGFueXRoaW5nIGluc2lkZSBpdCAoZWxzZSBgYGAgaW4gdGhlIGNvbnRlbnQgY2xvc2VzIGVhcmx5KS4KICAgIGZ1bmN0aW9uIGJhY2t0aWNrUnVuKHMpIHsKICAgICAgdmFyIG1heCA9IDAsIGN1ciA9IDA7CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgcy5sZW5ndGg7IGkrKykgewogICAgICAgIGlmIChzLmNoYXJBdChpKSA9PT0gImAiKSB7IGN1cisrOyBpZiAoY3VyID4gbWF4KSBtYXggPSBjdXI7IH0gZWxzZSBjdXIgPSAwOwogICAgICB9CiAgICAgIHJldHVybiBtYXg7CiAgICB9CiAgICBmdW5jdGlvbiBmZW5jZShzLCBtaW4pIHsgdmFyIG4gPSBiYWNrdGlja1J1bihzKSArIDE7IGlmIChuIDwgbWluKSBuID0gbWluOyByZXR1cm4gbmV3IEFycmF5KG4gKyAxKS5qb2luKCJgIik7IH0KICAgIGZ1bmN0aW9uIGlubGluZShub2RlKSB7CiAgICAgIHZhciBvdXQgPSAiIjsKICAgICAgdmFyIGtpZHMgPSBub2RlLmNoaWxkTm9kZXMgfHwgW107CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwga2lkcy5sZW5ndGg7IGkrKykgewogICAgICAgIHZhciBjID0ga2lkc1tpXTsKICAgICAgICBpZiAoYy5ub2RlVHlwZSA9PT0gMykgeyBvdXQgKz0gYy50ZXh0Q29udGVudCB8fCAiIjsgY29udGludWU7IH0KICAgICAgICBpZiAoYy5ub2RlVHlwZSAhPT0gMSkgY29udGludWU7CiAgICAgICAgdmFyIHRhZyA9IChjLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCk7CiAgICAgICAgaWYgKHRhZyA9PT0gIkJSIikgb3V0ICs9ICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiU1RST05HIiB8fCB0YWcgPT09ICJCIikgb3V0ICs9ICIqKiIgKyBpbmxpbmUoYykgKyAiKioiOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIkVNIiB8fCB0YWcgPT09ICJJIikgb3V0ICs9ICIqIiArIGlubGluZShjKSArICIqIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJERUwiIHx8IHRhZyA9PT0gIlMiKSBvdXQgKz0gIn5+IiArIGlubGluZShjKSArICJ+fiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiQ09ERSIpIHsKICAgICAgICAgIHZhciBjdCA9IGMudGV4dENvbnRlbnQgfHwgIiI7CiAgICAgICAgICB2YXIgZCA9IGZlbmNlKGN0LCAxKTsKICAgICAgICAgIC8vIENvbW1vbk1hcmsgc3RyaXBzIG9uZSBsZWFkaW5nK3RyYWlsaW5nIHNwYWNlLCBzbyBwYWQgd2hlbiBhbiBlZGdlIGlzIGEKICAgICAgICAgIC8vIGJhY2t0aWNrIHRvIGtlZXAgaXQgZnJvbSBtZXJnaW5nIHdpdGggdGhlIGRlbGltaXRlci4KICAgICAgICAgIHZhciBwID0gKGN0LmNoYXJBdCgwKSA9PT0gImAiIHx8IGN0LmNoYXJBdChjdC5sZW5ndGggLSAxKSA9PT0gImAiKSA/ICIgIiA6ICIiOwogICAgICAgICAgb3V0ICs9IGQgKyBwICsgY3QgKyBwICsgZDsKICAgICAgICB9CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiQSIpIHsKICAgICAgICAgIHZhciBocmVmID0gYy5nZXRBdHRyaWJ1dGUgPyBjLmdldEF0dHJpYnV0ZSgiaHJlZiIpIDogbnVsbDsKICAgICAgICAgIHZhciB0ID0gaW5saW5lKGMpOwogICAgICAgICAgb3V0ICs9IGhyZWYgPyAiWyIgKyB0ICsgIl0oIiArIGhyZWYgKyAiKSIgOiB0OwogICAgICAgIH0gZWxzZSBvdXQgKz0gaW5saW5lKGMpOyAvLyB1bmtub3duIGlubGluZSB3cmFwcGVyOiBrZWVwIHRleHQsIGRyb3AgdGFnCiAgICAgIH0KICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIGZ1bmN0aW9uIGxhbmdPZihjb2RlRWwpIHsKICAgICAgdmFyIGNscyA9ICIiOwogICAgICBpZiAoY29kZUVsKSBjbHMgPSAoY29kZUVsLmdldEF0dHJpYnV0ZSAmJiBjb2RlRWwuZ2V0QXR0cmlidXRlKCJjbGFzcyIpKSB8fCBjb2RlRWwuY2xhc3NOYW1lIHx8ICIiOwogICAgICB2YXIgbSA9IC9sYW5ndWFnZS0oW0EtWmEtejAtOSsjLlwtXSspLy5leGVjKGNscyB8fCAiIik7CiAgICAgIHJldHVybiBtID8gbVsxXSA6ICIiOwogICAgfQogICAgZnVuY3Rpb24gZmluZENoaWxkVGFnKG5vZGUsIHRhZykgewogICAgICB2YXIga2lkcyA9IG5vZGUuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgaWYgKGtpZHNbaV0ubm9kZVR5cGUgPT09IDEgJiYgKGtpZHNbaV0udGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKSA9PT0gdGFnKSByZXR1cm4ga2lkc1tpXTsKICAgICAgfQogICAgICByZXR1cm4gbnVsbDsKICAgIH0KICAgIGZ1bmN0aW9uIGxpc3Qobm9kZSwgb3JkZXJlZCwgZGVwdGgpIHsKICAgICAgdmFyIG91dCA9ICIiLCBuID0gMTsKICAgICAgdmFyIGtpZHMgPSBub2RlLmNoaWxkTm9kZXMgfHwgW107CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwga2lkcy5sZW5ndGg7IGkrKykgewogICAgICAgIHZhciBsaSA9IGtpZHNbaV07CiAgICAgICAgaWYgKGxpLm5vZGVUeXBlICE9PSAxIHx8IChsaS50YWdOYW1lIHx8ICIiKS50b1VwcGVyQ2FzZSgpICE9PSAiTEkiKSBjb250aW51ZTsKICAgICAgICB2YXIgbWFya2VyID0gb3JkZXJlZCA/IG4rKyArICIuICIgOiAiLSAiOwogICAgICAgIHZhciBpbmRlbnQgPSBuZXcgQXJyYXkoZGVwdGggKyAxKS5qb2luKCIgICIpOwogICAgICAgIHZhciBsZWFkID0gIiIsIG5lc3RlZCA9ICIiOwogICAgICAgIHZhciBsayA9IGxpLmNoaWxkTm9kZXMgfHwgW107CiAgICAgICAgZm9yICh2YXIgaiA9IDA7IGogPCBsay5sZW5ndGg7IGorKykgewogICAgICAgICAgdmFyIGNoID0gbGtbal07CiAgICAgICAgICB2YXIgY3QgPSBjaC5ub2RlVHlwZSA9PT0gMSA/IChjaC50YWdOYW1lIHx8ICIiKS50b1VwcGVyQ2FzZSgpIDogIiI7CiAgICAgICAgICBpZiAoY3QgPT09ICJVTCIpIG5lc3RlZCArPSBsaXN0KGNoLCBmYWxzZSwgZGVwdGggKyAxKTsKICAgICAgICAgIGVsc2UgaWYgKGN0ID09PSAiT0wiKSBuZXN0ZWQgKz0gbGlzdChjaCwgdHJ1ZSwgZGVwdGggKyAxKTsKICAgICAgICAgIGVsc2UgaWYgKGNoLm5vZGVUeXBlID09PSAzKSBsZWFkICs9IGNoLnRleHRDb250ZW50IHx8ICIiOwogICAgICAgICAgZWxzZSBsZWFkICs9IGlubGluZShjaCk7CiAgICAgICAgfQogICAgICAgIG91dCArPSBpbmRlbnQgKyBtYXJrZXIgKyBsZWFkLnRyaW0oKSArICJcbiIgKyBuZXN0ZWQ7CiAgICAgIH0KICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIGZ1bmN0aW9uIHRhYmxlKG5vZGUpIHsKICAgICAgdmFyIHJvd3MgPSBbXTsKICAgICAgKGZ1bmN0aW9uIGNvbGxlY3QoY29udGFpbmVyKSB7CiAgICAgICAgdmFyIGtpZHMgPSBjb250YWluZXIuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGtpZHMubGVuZ3RoOyBpKyspIHsKICAgICAgICAgIHZhciBjID0ga2lkc1tpXTsKICAgICAgICAgIGlmIChjLm5vZGVUeXBlICE9PSAxKSBjb250aW51ZTsKICAgICAgICAgIHZhciB0ID0gKGMudGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKTsKICAgICAgICAgIGlmICh0ID09PSAiVEhFQUQiIHx8IHQgPT09ICJUQk9EWSIgfHwgdCA9PT0gIlRGT09UIikgY29sbGVjdChjKTsKICAgICAgICAgIGVsc2UgaWYgKHQgPT09ICJUUiIpIHsKICAgICAgICAgICAgdmFyIGNlbGxzID0gW10sIGNjID0gYy5jaGlsZE5vZGVzIHx8IFtdOwogICAgICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IGNjLmxlbmd0aDsgaisrKSB7CiAgICAgICAgICAgICAgdmFyIGQgPSBjY1tqXTsKICAgICAgICAgICAgICBpZiAoZC5ub2RlVHlwZSAhPT0gMSkgY29udGludWU7CiAgICAgICAgICAgICAgdmFyIGR0ID0gKGQudGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKTsKICAgICAgICAgICAgICBpZiAoZHQgPT09ICJUSCIgfHwgZHQgPT09ICJURCIpIGNlbGxzLnB1c2goaW5saW5lKGQpLnRyaW0oKSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgcm93cy5wdXNoKGNlbGxzKTsKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0pKG5vZGUpOwogICAgICBpZiAoIXJvd3MubGVuZ3RoKSByZXR1cm4gIiI7CiAgICAgIHZhciBoZWFkID0gcm93c1swXSwgYm9keSA9IHJvd3Muc2xpY2UoMSk7CiAgICAgIHZhciBzZXAgPSBoZWFkLm1hcChmdW5jdGlvbiAoKSB7IHJldHVybiAiLS0tIjsgfSk7CiAgICAgIHZhciBvdXQgPSAifCAiICsgaGVhZC5qb2luKCIgfCAiKSArICIgfFxufCAiICsgc2VwLmpvaW4oIiB8ICIpICsgIiB8XG4iOwogICAgICBmb3IgKHZhciBrID0gMDsgayA8IGJvZHkubGVuZ3RoOyBrKyspIG91dCArPSAifCAiICsgYm9keVtrXS5qb2luKCIgfCAiKSArICIgfFxuIjsKICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIGZ1bmN0aW9uIGJsb2NrKG5vZGUpIHsKICAgICAgdmFyIG91dCA9ICIiOwogICAgICB2YXIga2lkcyA9IG5vZGUuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgdmFyIGMgPSBraWRzW2ldOwogICAgICAgIGlmIChjLm5vZGVUeXBlID09PSAzKSB7IGlmICgoYy50ZXh0Q29udGVudCB8fCAiIikudHJpbSgpKSBvdXQgKz0gYy50ZXh0Q29udGVudDsgY29udGludWU7IH0KICAgICAgICBpZiAoYy5ub2RlVHlwZSAhPT0gMSkgY29udGludWU7CiAgICAgICAgdmFyIHRhZyA9IChjLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCk7CiAgICAgICAgaWYgKC9eSFsxLTZdJC8udGVzdCh0YWcpKSBvdXQgKz0gbmV3IEFycmF5KCt0YWdbMV0gKyAxKS5qb2luKCIjIikgKyAiICIgKyBpbmxpbmUoYykudHJpbSgpICsgIlxuXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIlAiKSBvdXQgKz0gaW5saW5lKGMpLnRyaW0oKSArICJcblxuIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJVTCIpIG91dCArPSBsaXN0KGMsIGZhbHNlLCAwKSArICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiT0wiKSBvdXQgKz0gbGlzdChjLCB0cnVlLCAwKSArICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiUFJFIikgewogICAgICAgICAgdmFyIGNvZGUgPSBmaW5kQ2hpbGRUYWcoYywgIkNPREUiKTsKICAgICAgICAgIHZhciBsYW5nID0gbGFuZ09mKGNvZGUgfHwgYyk7CiAgICAgICAgICB2YXIgYm9keSA9IChjb2RlIHx8IGMpLnRleHRDb250ZW50IHx8ICIiOwogICAgICAgICAgdmFyIGYgPSBmZW5jZShib2R5LCAzKTsKICAgICAgICAgIG91dCArPSBmICsgbGFuZyArICJcbiIgKyBib2R5LnJlcGxhY2UoL1xuJC8sICIiKSArICJcbiIgKyBmICsgIlxuXG4iOwogICAgICAgIH0gZWxzZSBpZiAodGFnID09PSAiQkxPQ0tRVU9URSIpIHsKICAgICAgICAgIHZhciBpbm5lciA9IGJsb2NrKGMpLnRyaW0oKS5zcGxpdCgiXG4iKS5tYXAoZnVuY3Rpb24gKGwpIHsgcmV0dXJuICI+ICIgKyBsOyB9KS5qb2luKCJcbiIpOwogICAgICAgICAgb3V0ICs9IGlubmVyICsgIlxuXG4iOwogICAgICAgIH0gZWxzZSBpZiAodGFnID09PSAiSFIiKSBvdXQgKz0gIi0tLVxuXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIlRBQkxFIikgb3V0ICs9IHRhYmxlKGMpICsgIlxuIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJCUiIpIG91dCArPSAiXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIlNUUk9ORyIgfHwgdGFnID09PSAiQiIgfHwgdGFnID09PSAiRU0iIHx8IHRhZyA9PT0gIkkiIHx8CiAgICAgICAgICAgICAgICAgdGFnID09PSAiQSIgfHwgdGFnID09PSAiQ09ERSIgfHwgdGFnID09PSAiREVMIiB8fCB0YWcgPT09ICJTIikKICAgICAgICAgIG91dCArPSBpbmxpbmUoYykgKyAiXG5cbiI7CiAgICAgICAgZWxzZSBvdXQgKz0gYmxvY2soYyk7IC8vIHVua25vd24gd3JhcHBlcjogcmVjdXJzZSAoZHJvcCB0YWcsIGtlZXAgY29udGVudCkKICAgICAgfQogICAgICByZXR1cm4gb3V0OwogICAgfQogICAgLy8gYmxvY2soKSBkaXNwYXRjaGVzIG9uIGVhY2ggQ0hJTEQncyB0YWcsIHRyZWF0aW5nIHRoZSBwYXNzZWQgbm9kZSBhcyBhIHBsYWluCiAgICAvLyBjb250YWluZXIuIFdyYXAgcm9vdCBpbiBhIG9uZS1vZmYgY29udGFpbmVyIHNvIHJvb3QncyBPV04gdGFnIGlzIGRpc3BhdGNoZWQKICAgIC8vIHRvbzogY2FsbGVycyBwYXNzIGVpdGhlciB0aGUgYnViYmxlIGNvbnRhaW5lciAoaXRzIGJsb2NrIGNoaWxkcmVuIHJlbmRlcikgb3IKICAgIC8vIGEgc2luZ2xlIGJsb2NrIGVsZW1lbnQgbGlrZSA8cHJlPi88dWw+Lzx0YWJsZT4gKG5vdyBoYW5kbGVkLCBub3QgZmxhdHRlbmVkKS4KICAgIHJldHVybiBibG9jayh7IGNoaWxkTm9kZXM6IFtyb290XSB9KS5yZXBsYWNlKC9cbnszLH0vZywgIlxuXG4iKS50cmltKCk7CiAgfQoKICAvLyAtLS0tIHB1cmUgaGVscGVycyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgZnVuY3Rpb24gaGFzUHJlZml4KG5vZGUsIHByZWZpeCkgewogICAgaWYgKG5vZGUubm9kZVR5cGUgIT09IDEgfHwgdHlwZW9mIG5vZGUuY2xhc3NOYW1lICE9PSAic3RyaW5nIikgcmV0dXJuIGZhbHNlOwogICAgdmFyIHBhcnRzID0gbm9kZS5jbGFzc05hbWUuc3BsaXQoL1xzKy8pOwogICAgZm9yICh2YXIgaSA9IDA7IGkgPCBwYXJ0cy5sZW5ndGg7IGkrKykgaWYgKHBhcnRzW2ldLmluZGV4T2YocHJlZml4KSA9PT0gMCkgcmV0dXJuIHRydWU7CiAgICByZXR1cm4gZmFsc2U7CiAgfQoKICAvLyBDbGFzcy1wcmVmaXggaG9va3MgZm9yIG5vbi1jb250ZW50IGNocm9tZSB0aGF0IHJlbmRlcnMgKmluc2lkZSogYW4gYXNzaXN0YW50CiAgLy8gYnViYmxlICh2ZXJpZmllZCBvbiAyLjEuMTcwOyBUYXNrIDYgcmUtcGlucyB0aGVzZSkuIHRvb2wqL3RoaW5raW5nXyBhcmUgdGhlIHYxCiAgLy8gZXhjbHVzaW9uczsgdW5rbm93bkNvbnRlbnRfIGlzIHRoZSByZW5kZXJlcidzIGZhbGxiYWNrIGZvciB1bnJlY29nbml6ZWQgYmxvY2sKICAvLyB0eXBlcywgc28gc3RyaXBwaW5nIGl0IG1ha2VzIGEgKmZ1dHVyZSogYmxvY2sgdHlwZSBmYWlsIHNhZmUgdG8gZXhjbHVkZWQgcmF0aGVyCiAgLy8gdGhhbiBsZWFraW5nICJVbnN1cHBvcnRlZCBjb250ZW50IiBpbnRvIHRoZSBjb3B5LiBSZS1waW4gaWYgYSBwcmVmaXggbW92ZXMuCiAgdmFyIENIUk9NRV9QUkVGSVhFUyA9IFsidG9vbFVzZV8iLCAidG9vbFJlc3VsdF8iLCAidG9vbFJlZmVyZW5jZV8iLCAidGhpbmtpbmdfIiwgInVua25vd25Db250ZW50XyJdOwoKICAvLyBUcnVlIGZvciBhbnkgbm9kZSB0aGF0IG11c3QgbmV2ZXIgYXBwZWFyIGluIGNvcGllZCBvdXRwdXQ6IG91ciBvd24gY29udHJvbHMsCiAgLy8gdGhlIHJhdGluZyB3aWRnZXQgKGBkYXRhLW1lc3NhZ2UtcmF0aW5nYCArIGl0cyAiVGhhbmtzIGZvciB5b3VyIGZlZWRiYWNrIgogIC8vIHRleHQpLCBhbnkgYnV0dG9uIChjb3B5LWNvZGUgY2hyb21lKSwgYW5kIHRoZSBleGNsdWRlZCBjb250ZW50IGJsb2NrcyBhYm92ZS4KICBmdW5jdGlvbiBpc0Nocm9tZShub2RlKSB7CiAgICBpZiAobm9kZS5ub2RlVHlwZSAhPT0gMSkgcmV0dXJuIGZhbHNlOwogICAgaWYgKChub2RlLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCkgPT09ICJCVVRUT04iKSByZXR1cm4gdHJ1ZTsKICAgIGlmIChub2RlLmdldEF0dHJpYnV0ZSAmJiBub2RlLmdldEF0dHJpYnV0ZSgiZGF0YS1tZXNzYWdlLXJhdGluZyIpICE9PSBudWxsKSByZXR1cm4gdHJ1ZTsKICAgIGlmIChoYXNQcmVmaXgobm9kZSwgQ09OVFJPTF9QUkVGSVgpKSByZXR1cm4gdHJ1ZTsKICAgIGZvciAodmFyIGkgPSAwOyBpIDwgQ0hST01FX1BSRUZJWEVTLmxlbmd0aDsgaSsrKSBpZiAoaGFzUHJlZml4KG5vZGUsIENIUk9NRV9QUkVGSVhFU1tpXSkpIHJldHVybiB0cnVlOwogICAgcmV0dXJuIGZhbHNlOwogIH0KCiAgLy8gRGVlcC1jbG9uZSBgY29udGVudE5vZGVgLCB0aGVuIHN0cmlwIGV2ZXJ5IGNocm9tZSBub2RlIHNvIGNvcGllZCBvdXRwdXQgaXMgdGhlCiAgLy8gbWVzc2FnZSdzIHRleHQgY29udGVudCBvbmx5LiBUaGlzIGlzIGEgQ09SUkVDVE5FU1MgR0FURSwgbm90IGNvc21ldGljOiB0aGUKICAvLyBkZWZhdWx0IGNvbnRlbnQgbm9kZSBpcyB0aGUgd2hvbGUgYnViYmxlIChhbGwgY29udGVudC1ibG9jayBzaWJsaW5ncywgc28gbXVsdGktCiAgLy8gYmxvY2sgYXNzaXN0YW50IHR1cm5zIGFyZSBjYXB0dXJlZCksIGFuZCB0aGlzIHN0cmlwLWxpc3QgaXMgdGhlIG9ubHkgdGhpbmcKICAvLyBrZWVwaW5nIHRoZSByYXRpbmcgd2lkZ2V0IGFuZCB2MS1leGNsdWRlZCBibG9ja3Mgb3V0IG9mIHRoZSBjb3B5LgogIGZ1bmN0aW9uIHNhbml0aXplQ2xvbmUoY29udGVudE5vZGUpIHsKICAgIHZhciBjbG9uZSA9IGNvbnRlbnROb2RlLmNsb25lTm9kZSh0cnVlKTsKICAgIChmdW5jdGlvbiBzdHJpcChub2RlKSB7CiAgICAgIHZhciBraWRzID0gKG5vZGUuY2hpbGROb2RlcyB8fCBbXSkuc2xpY2UoKTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgdmFyIGMgPSBraWRzW2ldOwogICAgICAgIGlmIChjLm5vZGVUeXBlID09PSAxICYmIGlzQ2hyb21lKGMpKSB7IG5vZGUucmVtb3ZlQ2hpbGQoYyk7IGNvbnRpbnVlOyB9CiAgICAgICAgaWYgKGMubm9kZVR5cGUgPT09IDEpIHN0cmlwKGMpOwogICAgICB9CiAgICB9KShjbG9uZSk7CiAgICByZXR1cm4gY2xvbmU7CiAgfQoKICBmdW5jdGlvbiBjbGFzc2lmeUJ1YmJsZShub2RlKSB7CiAgICBpZiAobm9kZS5ub2RlVHlwZSAhPT0gMSkgcmV0dXJuIG51bGw7CiAgICBpZiAoaGFzUHJlZml4KG5vZGUsICJ1c2VyTWVzc2FnZUNvbnRhaW5lcl8iKSkgcmV0dXJuICJ1c2VyIjsKICAgIGlmIChub2RlLmdldEF0dHJpYnV0ZSAmJiBub2RlLmdldEF0dHJpYnV0ZSgiZGF0YS10ZXN0aWQiKSA9PT0gImFzc2lzdGFudC1tZXNzYWdlIikgcmV0dXJuICJhc3Npc3RhbnQiOwogICAgcmV0dXJuIG51bGw7CiAgfQoKICAvLyBCdWlsZCB0aGUgd2hvbGUtY29udmVyc2F0aW9uIG1hcmtkb3duIGZyb20gYW4gb3JkZXJlZCBsaXN0IG9mIGJ1YmJsZXMuCiAgLy8gYGNvbnRlbnRPZihidWJibGUpYCByZXNvbHZlcyB0aGUgY29udGVudCBub2RlIChkZWZhdWx0OiB0aGUgYnViYmxlIGl0c2VsZiwgc28KICAvLyBldmVyeSBjb250ZW50IGJsb2NrIGlzIGluY2x1ZGVkOyBzYW5pdGl6ZUNsb25lIGRyb3BzIGNocm9tZSk7IGEgZGVmYXVsdCBpcwogIC8vIHByb3ZpZGVkIGZvciB0ZXN0cy4KICBmdW5jdGlvbiBjb252ZXJzYXRpb25Ub01hcmtkb3duKGJ1YmJsZXMsIGNvbnRlbnRPZikgewogICAgY29udGVudE9mID0gY29udGVudE9mIHx8IGZ1bmN0aW9uIChiKSB7IHJldHVybiBiOyB9OwogICAgdmFyIHBhcnRzID0gW107CiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGJ1YmJsZXMubGVuZ3RoOyBpKyspIHsKICAgICAgdmFyIHJvbGUgPSBjbGFzc2lmeUJ1YmJsZShidWJibGVzW2ldKTsKICAgICAgaWYgKCFyb2xlKSBjb250aW51ZTsKICAgICAgdmFyIGNsZWFuID0gc2FuaXRpemVDbG9uZShjb250ZW50T2YoYnViYmxlc1tpXSkpOwogICAgICB2YXIgYm9keSA9IHJvbGUgPT09ICJhc3Npc3RhbnQiID8gaHRtbFRvTWFya2Rvd24oY2xlYW4pIDogKGNsZWFuLnRleHRDb250ZW50IHx8ICIiKS50cmltKCk7CiAgICAgIGlmICghYm9keSkgY29udGludWU7CiAgICAgIHBhcnRzLnB1c2goKHJvbGUgPT09ICJ1c2VyIiA/ICIjIyBVc2VyIiA6ICIjIyBBc3Npc3RhbnQiKSArICJcblxuIiArIGJvZHkpOwogICAgfQogICAgcmV0dXJuIHBhcnRzLmpvaW4oIlxuXG4iKSArIChwYXJ0cy5sZW5ndGggPyAiXG4iIDogIiIpOwogIH0KCiAgLy8gLS0tLSBleHBvcnRzIChub2RlIHRlc3RzKSAvIGJvb3QgKHJlYWwgd2VidmlldykgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogIGlmICh0eXBlb2YgZG9jdW1lbnQgIT09ICJ1bmRlZmluZWQiKSB7CiAgICBib290KCk7CiAgfSBlbHNlIGlmICh0eXBlb2YgbW9kdWxlICE9PSAidW5kZWZpbmVkIiAmJiBtb2R1bGUuZXhwb3J0cykgewogICAgbW9kdWxlLmV4cG9ydHMgPSB7IGh0bWxUb01hcmtkb3duOiBodG1sVG9NYXJrZG93biwgc2FuaXRpemVDbG9uZTogc2FuaXRpemVDbG9uZSwKICAgICAgICAgICAgICAgICAgICAgICBjbGFzc2lmeUJ1YmJsZTogY2xhc3NpZnlCdWJibGUsIGNvbnZlcnNhdGlvblRvTWFya2Rvd246IGNvbnZlcnNhdGlvblRvTWFya2Rvd24sCiAgICAgICAgICAgICAgICAgICAgICAgY29weVRleHQ6IGNvcHlUZXh0IH07CiAgfQoKICAvLyAtLS0tIGxpdmUtd2VidmlldyB3aXJpbmcgKHJ1bnMgb25seSB3aGVuIGEgZG9jdW1lbnQgZXhpc3RzKSAtLS0tLS0tLS0tLS0tLS0tCiAgZnVuY3Rpb24gcXMobm9kZSwgc2VsKSB7IHRyeSB7IHJldHVybiBzZWwgJiYgbm9kZS5xdWVyeVNlbGVjdG9yID8gbm9kZS5xdWVyeVNlbGVjdG9yKHNlbCkgOiBudWxsOyB9IGNhdGNoIChfKSB7IHJldHVybiBudWxsOyB9IH0KICBmdW5jdGlvbiBxc2Eoc2VsKSB7IHRyeSB7IHJldHVybiBBcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKHNlbCkpOyB9IGNhdGNoIChfKSB7IHJldHVybiBbXTsgfSB9CgogIC8vIFRoZSBjb250ZW50IG5vZGUgdG8gY29udmVydC9jb3B5OiB0aGUgb3B0aW9uYWwgQVNTSVNUQU5UX0NPTlRFTlQgd3JhcHBlciBpZgogIC8vIHBpbm5lZCBhbmQgcHJlc2VudCwgZWxzZSB0aGUgYnViYmxlIGl0c2VsZi4gVGhlIGJ1YmJsZSBhbHJlYWR5IGNvbnRhaW5zIGV2ZXJ5CiAgLy8gY29udGVudC1ibG9jayBzaWJsaW5nIG9mIGEgbXVsdGktYmxvY2sgdHVybiwgYW5kIHNhbml0aXplQ2xvbmUgc3RyaXBzIHRoZQogIC8vIGNocm9tZSAocmF0aW5nIHdpZGdldCwgdG9vbC90aGlua2luZy91bmtub3duIGJsb2NrcywgYnV0dG9ucywgb3VyIGNvbnRyb2xzKQogIC8vIGVpdGhlciB3YXkgLS0gc28gdGhpcyBpcyBhIG5hcnJvd2luZywgbmV2ZXIgdGhlIHRoaW5nIHRoYXQgZ3VhcmFudGVlcwogIC8vIGNvcnJlY3RuZXNzLgogIGZ1bmN0aW9uIGNvbnRlbnROb2RlT2YoYnViYmxlLCByb2xlKSB7CiAgICBpZiAocm9sZSA9PT0gImFzc2lzdGFudCIgJiYgQVNTSVNUQU5UX0NPTlRFTlQpIHsKICAgICAgdmFyIG4gPSBxcyhidWJibGUsIEFTU0lTVEFOVF9DT05URU5UKTsKICAgICAgaWYgKG4pIHJldHVybiBuOwogICAgfQogICAgcmV0dXJuIGJ1YmJsZTsKICB9CgogIC8vIENvcHkgYHNgIHZpYSBhIHN5bmNocm9ub3VzIGV4ZWNDb21tYW5kKCJjb3B5Iikgb24gYW4gb2ZmLXNjcmVlbiB0ZXh0YXJlYSwgYW5kCiAgLy8gcmVwb3J0IHdoZXRoZXIgaXQgYWN0dWFsbHkgaGFwcGVuZWQuIERvbmUgZmlyc3QgKGFuZCBzeW5jaHJvbm91c2x5KSBiZWNhdXNlIGl0CiAgLy8gcnVucyBpbnNpZGUgdGhlIGNsaWNrIGdlc3R1cmUgYW5kIHdvcmtzIHdoZXRoZXIgb3Igbm90IHRoZSBwYWdlIGlzIGEgc2VjdXJlCiAgLy8gY29udGV4dCAtLSBzbyBpdCBjb3ZlcnMgcmVtb3RlIC8gY29kZS1zZXJ2ZXIsIHdoZXJlIHRoZSBhc3luYyBDbGlwYm9hcmQgQVBJIGlzCiAgLy8gc2ltcGx5IGFic2VudC4gUmVzdG9yZXMgdGhlIHByaW9yIHNlbGVjdGlvbi9mb2N1cyBzbyBpdCBpcyBpbnZpc2libGUuCiAgZnVuY3Rpb24gZXhlY0NvcHkocykgewogICAgdHJ5IHsKICAgICAgaWYgKHR5cGVvZiBkb2N1bWVudCA9PT0gInVuZGVmaW5lZCIgfHwgIWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQpIHJldHVybiBmYWxzZTsKICAgICAgdmFyIHByZXYgPSBkb2N1bWVudC5hY3RpdmVFbGVtZW50IHx8IG51bGw7CiAgICAgIHZhciBzZWwgPSBkb2N1bWVudC5nZXRTZWxlY3Rpb24gPyBkb2N1bWVudC5nZXRTZWxlY3Rpb24oKSA6IG51bGw7CiAgICAgIHZhciBzYXZlZCA9IChzZWwgJiYgc2VsLnJhbmdlQ291bnQpID8gc2VsLmdldFJhbmdlQXQoMCkgOiBudWxsOwogICAgICB2YXIgdGEgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJ0ZXh0YXJlYSIpOwogICAgICB0YS52YWx1ZSA9IHM7CiAgICAgIHRhLnNldEF0dHJpYnV0ZSgicmVhZG9ubHkiLCAiIik7CiAgICAgIHRhLnN0eWxlLnBvc2l0aW9uID0gImZpeGVkIjsKICAgICAgdGEuc3R5bGUudG9wID0gIi0xMDAwcHgiOwogICAgICB0YS5zdHlsZS5sZWZ0ID0gIjAiOwogICAgICB0YS5zdHlsZS5vcGFjaXR5ID0gIjAiOwogICAgICAoZG9jdW1lbnQuYm9keSB8fCBkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQpLmFwcGVuZENoaWxkKHRhKTsKICAgICAgdGEuZm9jdXMoKTsKICAgICAgdGEuc2VsZWN0KCk7CiAgICAgIHZhciBvayA9IGZhbHNlOwogICAgICB0cnkgeyBvayA9IGRvY3VtZW50LmV4ZWNDb21tYW5kKCJjb3B5Iik7IH0gY2F0Y2ggKF8pIHsgb2sgPSBmYWxzZTsgfQogICAgICBpZiAodGEucGFyZW50Tm9kZSkgdGEucGFyZW50Tm9kZS5yZW1vdmVDaGlsZCh0YSk7CiAgICAgIGlmIChzYXZlZCAmJiBzZWwpIHsgdHJ5IHsgc2VsLnJlbW92ZUFsbFJhbmdlcygpOyBzZWwuYWRkUmFuZ2Uoc2F2ZWQpOyB9IGNhdGNoIChfKSB7fSB9CiAgICAgIGlmIChwcmV2ICYmIHByZXYuZm9jdXMpIHsgdHJ5IHsgcHJldi5mb2N1cygpOyB9IGNhdGNoIChfKSB7fSB9CiAgICAgIHJldHVybiAhIW9rOwogICAgfSBjYXRjaCAoXykgeyByZXR1cm4gZmFsc2U7IH0KICB9CgogIC8vIENvcHkgYHRleHRgIGFuZCByZXNvbHZlIHRvIHdoZXRoZXIgdGhlIGNvcHkgQUNUVUFMTFkgaGFwcGVuZWQsIHNvIGNhbGxlcnMgb25seQogIC8vIHNob3cgc3VjY2VzcyBvbiBhIHJlYWwgY29weSAtLSBuZXZlciBhIGZhbHNlICJjb3BpZWQiICh0aGUgb3JpZ2luYWwgYnVnOgogIC8vIG5hdmlnYXRvci5jbGlwYm9hcmQgd2FzIHVuZGVmaW5lZCBpbiB0aGUgd2VidmlldywgdGhlIGNvZGUgZmVsbCB0aHJvdWdoIHRvCiAgLy8gUHJvbWlzZS5yZXNvbHZlKCksIGFuZCB0aGUgVUkgY2xhaW1lZCBzdWNjZXNzIHdoaWxlIG5vdGhpbmcgd2FzIHdyaXR0ZW4pLiBFbXB0eQogIC8vIHRleHQgaXMgYSBub24tY29weSAtPiBmYWxzZS4gZXhlY0NvbW1hbmQgZmlyc3QgKGdlc3R1cmUtc2FmZSwgc2VjdXJlLWNvbnRleHQtCiAgLy8gaW5kZXBlbmRlbnQpOyB0aGUgYXN5bmMgQ2xpcGJvYXJkIEFQSSBpcyB0aGUgZmFsbGJhY2suIE5ldmVyIHRocm93cy4KICBmdW5jdGlvbiBjb3B5VGV4dCh0ZXh0KSB7CiAgICB2YXIgcyA9ICh0ZXh0ID09IG51bGwpID8gIiIgOiBTdHJpbmcodGV4dCk7CiAgICBpZiAoIXMpIHJldHVybiBQcm9taXNlLnJlc29sdmUoZmFsc2UpOwogICAgaWYgKGV4ZWNDb3B5KHMpKSByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKHRydWUpOwogICAgdHJ5IHsKICAgICAgaWYgKHR5cGVvZiBuYXZpZ2F0b3IgIT09ICJ1bmRlZmluZWQiICYmIG5hdmlnYXRvci5jbGlwYm9hcmQgJiYgbmF2aWdhdG9yLmNsaXBib2FyZC53cml0ZVRleHQpIHsKICAgICAgICByZXR1cm4gbmF2aWdhdG9yLmNsaXBib2FyZC53cml0ZVRleHQocykudGhlbigKICAgICAgICAgIGZ1bmN0aW9uICgpIHsgcmV0dXJuIHRydWU7IH0sCiAgICAgICAgICBmdW5jdGlvbiAoKSB7IHJldHVybiBmYWxzZTsgfQogICAgICAgICk7CiAgICAgIH0KICAgIH0gY2F0Y2ggKF8pIHt9CiAgICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKGZhbHNlKTsKICB9CgogIGZ1bmN0aW9uIGJ1YmJsZU1hcmtkb3duKGJ1YmJsZSwgcm9sZSkgewogICAgdmFyIGNsZWFuID0gc2FuaXRpemVDbG9uZShjb250ZW50Tm9kZU9mKGJ1YmJsZSwgcm9sZSkpOwogICAgcmV0dXJuIHJvbGUgPT09ICJhc3Npc3RhbnQiID8gaHRtbFRvTWFya2Rvd24oY2xlYW4pIDogKGNsZWFuLnRleHRDb250ZW50IHx8ICIiKS50cmltKCk7CiAgfQoKICAvLyBJbmxpbmUgU1ZHIGljb25zIChjdXJyZW50Q29sb3IsIH4xNHB4KS4gU2V0IHZpYSBpbm5lckhUTUwgb24gb3VyIG93biBidXR0b25zCiAgLy8gb25seTsgdGhlIG1hcmt1cCBuZXZlciByZWFjaGVzIGNvcGllZCBjb250ZW50IChzYW5pdGl6ZUNsb25lIGRyb3BzIG91ciBub2RlcykuCiAgdmFyIElDT05fQ09QWSA9ICc8c3ZnIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGFyaWEtaGlkZGVuPSJ0cnVlIj48cmVjdCB4PSI5IiB5PSI5IiB3aWR0aD0iMTMiIGhlaWdodD0iMTMiIHJ4PSIyIiByeT0iMiI+PC9yZWN0PjxwYXRoIGQ9Ik01IDE1SDRhMiAyIDAgMCAxLTItMlY0YTIgMiAwIDAgMSAyLTJoOWEyIDIgMCAwIDEgMiAydjEiPjwvcGF0aD48L3N2Zz4nOwogIHZhciBJQ09OX0NIRUNLID0gJzxzdmcgd2lkdGg9IjE0IiBoZWlnaHQ9IjE0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjIuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBhcmlhLWhpZGRlbj0idHJ1ZSI+PHBvbHlsaW5lIHBvaW50cz0iMjAgNiA5IDE3IDQgMTIiPjwvcG9seWxpbmU+PC9zdmc+JzsKCiAgLy8gRmxpcCB0aGUgYnV0dG9uIHRvIGEgY2hlY2ttYXJrIGZvciBGRUVEQkFDS19NUywgdGhlbiByZXN0b3JlLiBJZGVtcG90ZW50IGFjcm9zcwogIC8vIHJhcGlkIGNsaWNrcyAoYW55IHBlbmRpbmcgcmVzdG9yZSBpcyBjbGVhcmVkIGZpcnN0KS4KICBmdW5jdGlvbiBzaG93Q29waWVkKGJ0bikgewogICAgdHJ5IHsKICAgICAgaWYgKGJ0bi5fX2NjVGltZXIpIGNsZWFyVGltZW91dChidG4uX19jY1RpbWVyKTsKICAgICAgYnRuLmNsYXNzTGlzdC5hZGQoQ09OVFJPTF9QUkVGSVggKyAiLW9rIik7CiAgICAgIGJ0bi5pbm5lckhUTUwgPSBJQ09OX0NIRUNLOwogICAgICBidG4uX19jY1RpbWVyID0gc2V0VGltZW91dChmdW5jdGlvbiAoKSB7CiAgICAgICAgdHJ5IHsgYnRuLmNsYXNzTGlzdC5yZW1vdmUoQ09OVFJPTF9QUkVGSVggKyAiLW9rIik7IGJ0bi5pbm5lckhUTUwgPSBJQ09OX0NPUFk7IH0gY2F0Y2ggKF8pIHt9CiAgICAgICAgYnRuLl9fY2NUaW1lciA9IG51bGw7CiAgICAgIH0sIEZFRURCQUNLX01TKTsKICAgIH0gY2F0Y2ggKF8pIHt9CiAgfQoKICAvLyBCdWlsZCBhIHNpbmdsZSBjb250cm9sOiBvbmUgY2xpcGJvYXJkLWljb24gYnV0dG9uLiBgb25Db3B5KClgIGlzIGludm9rZWQKICAvLyBzeW5jaHJvbm91c2x5IG9uIGNsaWNrIChzbyB0aGUgY29weSBzdGF5cyBpbnNpZGUgdGhlIHVzZXIgZ2VzdHVyZSkgYW5kIG11c3QKICAvLyByZXR1cm4gYSBQcm9taXNlPGJvb2xlYW4+OyB0aGUgY2hlY2ttYXJrIHNob3dzIG9ubHkgd2hlbiBpdCByZXNvbHZlcyB0cnVlLiBBbGwKICAvLyBub2RlcyBjYXJyeSB0aGUgQ09OVFJPTF9QUkVGSVggY2xhc3Mgc28gc2FuaXRpemVDbG9uZSBzdHJpcHMgdGhlbSBmcm9tIGNvcGllcy4KICBmdW5jdGlvbiBidWlsZENvbnRyb2wob25Db3B5LCB0aXRsZSkgewogICAgdmFyIHdyYXAgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzcGFuIik7CiAgICB3cmFwLmNsYXNzTmFtZSA9IENPTlRST0xfUFJFRklYOwogICAgdmFyIGJ0biA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImJ1dHRvbiIpOwogICAgYnRuLnR5cGUgPSAiYnV0dG9uIjsKICAgIGJ0bi5jbGFzc05hbWUgPSBDT05UUk9MX1BSRUZJWCArICItYnRuIjsKICAgIGJ0bi50aXRsZSA9IHRpdGxlIHx8ICJDb3B5IGFzIE1hcmtkb3duIjsKICAgIGJ0bi5zZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWwiLCBidG4udGl0bGUpOwogICAgYnRuLmlubmVySFRNTCA9IElDT05fQ09QWTsKICAgIHZhciBidXN5ID0gZmFsc2U7CiAgICBidG4uYWRkRXZlbnRMaXN0ZW5lcigiY2xpY2siLCBmdW5jdGlvbiAoZSkgewogICAgICBlLnN0b3BQcm9wYWdhdGlvbigpOwogICAgICBpZiAoYnVzeSkgcmV0dXJuOwogICAgICBidXN5ID0gdHJ1ZTsKICAgICAgdmFyIHA7CiAgICAgIHRyeSB7IHAgPSBvbkNvcHkoKTsgfSBjYXRjaCAoXykgeyBwID0gZmFsc2U7IH0KICAgICAgUHJvbWlzZS5yZXNvbHZlKHApLnRoZW4oCiAgICAgICAgZnVuY3Rpb24gKG9rKSB7IGJ1c3kgPSBmYWxzZTsgaWYgKG9rKSBzaG93Q29waWVkKGJ0bik7IH0sCiAgICAgICAgZnVuY3Rpb24gKCkgeyBidXN5ID0gZmFsc2U7IH0KICAgICAgKTsKICAgIH0pOwogICAgd3JhcC5hcHBlbmRDaGlsZChidG4pOwogICAgcmV0dXJuIHdyYXA7CiAgfQoKICBmdW5jdGlvbiBkZWNvcmF0ZShidWJibGUpIHsKICAgIHRyeSB7CiAgICAgIHZhciByb2xlID0gY2xhc3NpZnlCdWJibGUoYnViYmxlKTsKICAgICAgaWYgKCFyb2xlKSByZXR1cm47CiAgICAgIC8vIElkZW1wb3RlbnQ6IGtlZXAgZXhhY3RseSBvbmUgY29udHJvbC4gQSBSZWFjdCByZS1yZW5kZXIgb2YgdGhlIGJ1YmJsZSBjYW4KICAgICAgLy8gbGVhdmUgYSBzdGFsZSBjb250cm9sIGJlaGluZCBvciB0cmFuc2llbnRseSBkZWZlYXQgYW4gImFscmVhZHkgZGVjb3JhdGVkIgogICAgICAvLyBndWFyZCwgd2hpY2ggaXMgd2hhdCBwcm9kdWNlZCBkdXBsaWNhdGUgcm93cyBvZiBidXR0b25zOyBwcnVuZSBhbnkgZXh0cmFzCiAgICAgIC8vIGV2ZXJ5IHN3ZWVwIGFuZCBvbmx5IGFkZCBvbmUgd2hlbiBub25lIHJlbWFpbi4KICAgICAgdmFyIGV4aXN0aW5nID0gYnViYmxlLnF1ZXJ5U2VsZWN0b3JBbGwgPyBidWJibGUucXVlcnlTZWxlY3RvckFsbCgiLiIgKyBDT05UUk9MX1BSRUZJWCkgOiBudWxsOwogICAgICBpZiAoZXhpc3RpbmcgJiYgZXhpc3RpbmcubGVuZ3RoKSB7CiAgICAgICAgZm9yICh2YXIgaSA9IGV4aXN0aW5nLmxlbmd0aCAtIDE7IGkgPj0gMTsgaS0tKSB7CiAgICAgICAgICBpZiAoZXhpc3RpbmdbaV0gJiYgZXhpc3RpbmdbaV0ucGFyZW50Tm9kZSkgZXhpc3RpbmdbaV0ucGFyZW50Tm9kZS5yZW1vdmVDaGlsZChleGlzdGluZ1tpXSk7CiAgICAgICAgfQogICAgICAgIHJldHVybjsKICAgICAgfQogICAgICB2YXIgY29udHJvbCA9IGJ1aWxkQ29udHJvbChmdW5jdGlvbiAoKSB7CiAgICAgICAgcmV0dXJuIGNvcHlUZXh0KGJ1YmJsZU1hcmtkb3duKGJ1YmJsZSwgcm9sZSkpOwogICAgICB9LCAiQ29weSBhcyBNYXJrZG93biIpOwogICAgICBidWJibGUuYXBwZW5kQ2hpbGQoY29udHJvbCk7CiAgICB9IGNhdGNoIChfKSB7fQogIH0KCiAgZnVuY3Rpb24gY29weUNvbnZlcnNhdGlvbigpIHsKICAgIHZhciBidWJibGVzID0gcXNhKFVTRVJfQlVCQkxFICsgIiwiICsgQVNTSVNUQU5UX0JVQkJMRSk7CiAgICByZXR1cm4gY29weVRleHQoY29udmVyc2F0aW9uVG9NYXJrZG93bihidWJibGVzLCBmdW5jdGlvbiAoYikgewogICAgICByZXR1cm4gY29udGVudE5vZGVPZihiLCBjbGFzc2lmeUJ1YmJsZShiKSk7CiAgICB9KSk7CiAgfQoKICAvLyBBIHNpbmdsZSBmbG9hdGluZyAiQ29weSBjb252ZXJzYXRpb24iIGljb24sIHByZXNlbnQgb25seSB3aGlsZSBhIGNvbnZlcnNhdGlvbgogIC8vIGlzIG9wZW4gKHNvIGl0IG5ldmVyIGNsdXR0ZXJzIHRoZSBoaXN0b3J5LWxpc3QgdmlldykuIFBpbm5lZCB0b3AtcmlnaHQgYnkgQ1NTLAogIC8vIGNsZWFyIG9mIHRoZSBjaGF0IGlucHV0IGF0IHRoZSBib3R0b207IHRoZSBtb3N0LXJlY2VudC1wcm9tcHQgc3RpY2t5IGhlYWRlcgogIC8vIHNpdHMgdG8gaXRzIGxlZnQuCiAgZnVuY3Rpb24gaW5zdGFsbENvbnZlcnNhdGlvbkNvbnRyb2woKSB7CiAgICB0cnkgewogICAgICB2YXIgZXhpc3RpbmcgPSBxcyhkb2N1bWVudCwgIi4iICsgQ09OVFJPTF9QUkVGSVggKyAiLWNvbnZlcnNhdGlvbiIpOwogICAgICB2YXIgaGFzTWVzc2FnZXMgPSBxc2EoVVNFUl9CVUJCTEUgKyAiLCIgKyBBU1NJU1RBTlRfQlVCQkxFKS5sZW5ndGggPiAwOwogICAgICBpZiAoIWhhc01lc3NhZ2VzKSB7CiAgICAgICAgaWYgKGV4aXN0aW5nICYmIGV4aXN0aW5nLnBhcmVudE5vZGUpIGV4aXN0aW5nLnBhcmVudE5vZGUucmVtb3ZlQ2hpbGQoZXhpc3RpbmcpOwogICAgICAgIHJldHVybjsKICAgICAgfQogICAgICBpZiAoZXhpc3RpbmcpIHJldHVybjsKICAgICAgdmFyIGJhciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpOwogICAgICBiYXIuY2xhc3NOYW1lID0gQ09OVFJPTF9QUkVGSVggKyAiLWNvbnZlcnNhdGlvbiI7CiAgICAgIGJhci5hcHBlbmRDaGlsZChidWlsZENvbnRyb2woY29weUNvbnZlcnNhdGlvbiwgIkNvcHkgY29udmVyc2F0aW9uIikpOwogICAgICBkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKGJhcik7CiAgICB9IGNhdGNoIChfKSB7fQogIH0KCiAgZnVuY3Rpb24gc3dlZXAoKSB7CiAgICB2YXIgYiA9IHFzYShVU0VSX0JVQkJMRSArICIsIiArIEFTU0lTVEFOVF9CVUJCTEUpOwogICAgZm9yICh2YXIgaSA9IDA7IGkgPCBiLmxlbmd0aDsgaSsrKSBkZWNvcmF0ZShiW2ldKTsKICAgIGluc3RhbGxDb252ZXJzYXRpb25Db250cm9sKCk7CiAgfQoKICBmdW5jdGlvbiBib290KCkgewogICAgdHJ5IHsKICAgICAgdmFyIHRhcmdldCA9IChNRVNTQUdFU19DT05UQUlORVIgJiYgcXMoZG9jdW1lbnQsIE1FU1NBR0VTX0NPTlRBSU5FUikpIHx8IGRvY3VtZW50LmJvZHk7CiAgICAgIHN3ZWVwKCk7CiAgICAgIGlmICh0eXBlb2YgTXV0YXRpb25PYnNlcnZlciA9PT0gInVuZGVmaW5lZCIpIHJldHVybjsKICAgICAgdmFyIG9icyA9IG5ldyBNdXRhdGlvbk9ic2VydmVyKGZ1bmN0aW9uICgpIHsgc3dlZXAoKTsgfSk7CiAgICAgIG9icy5vYnNlcnZlKHRhcmdldCwgeyBjaGlsZExpc3Q6IHRydWUsIHN1YnRyZWU6IHRydWUgfSk7CiAgICB9IGNhdGNoIChfKSB7fQogIH0KfSkoKTsK").decode("utf-8") +INJECT_JS = base64.b64decode("LyogY2MtbWQtY29weTogcGVyLW1lc3NhZ2UgYW5kIHdob2xlLWNvbnZlcnNhdGlvbiBjb3B5IChNYXJrZG93bikgZm9yIHRoZQogKiBDbGF1ZGUgQ29kZSBWUyBDb2RlIHdlYnZpZXcuIFNlbGYtY29udGFpbmVkIElJRkUgYXBwZW5kZWQgdG8gd2Vidmlldy9pbmRleC5qcy4KICogRWFjaCBjb250cm9sIGlzIGEgc2luZ2xlIGNsaXBib2FyZCBpY29uIHRoYXQgZmxpcHMgdG8gYSBjaGVja21hcmsgZm9yIH4ycyB3aGVuIGEKICogY29weSBhY3R1YWxseSBzdWNjZWVkcyAobm8gdGV4dCBsYWJlbCwgbm8gbWVudSkuIEFkZGl0aXZlIGFuZCByZWFkLW9ubHkgdy5yLnQuCiAqIGFwcCBzdGF0ZTsga2V5ZWQgb24gc3RhYmxlIENTUy1tb2R1bGUgY2xhc3MgcHJlZml4ZXMsIHNvIGl0IGZhaWxzIHNhZmUgKGNvbnRyb2xzCiAqIHNpbXBseSBkbyBub3QgYXBwZWFyKSBpZiBhIHByZWZpeCBtb3Zlcy4KICogRXhwb3NlcyBpdHMgcHVyZSBmdW5jdGlvbnMgZm9yIG5vZGUgdW5pdCB0ZXN0czsgYm9vdCgpcyBvbmx5IGluIGEgcmVhbCB3ZWJ2aWV3LiAqLwovKiBMZWFkaW5nICc7JyBzbyB0aGF0LCBhcHBlbmRlZCBhZnRlciB0aGUgYnVuZGxlLCB0aGlzIElJRkUgY2FuIG5ldmVyIGJlIHBhcnNlZCBhcwogKiBhIGNhbGwgb24gdGhlIGJ1bmRsZSdzIGZpbmFsIGV4cHJlc3Npb24gaWYgaXQgbGFja3MgYSB0cmFpbGluZyBzZW1pY29sb24gKEFTSQogKiBzYWZldHkgYWNyb3NzIGV4dGVuc2lvbiBidWlsZHMpLiAqLwo7KGZ1bmN0aW9uICgpIHsKICAidXNlIHN0cmljdCI7CgogIHZhciBDT05UUk9MX1BSRUZJWCA9ICJjYy1tZC1jb3B5IjsgLy8gZXZlcnkgaW5qZWN0ZWQgbm9kZSdzIGNsYXNzIHN0YXJ0cyB3aXRoIHRoaXMKICB2YXIgVVNFUl9CVUJCTEUgPSAnW2NsYXNzKj0idXNlck1lc3NhZ2VDb250YWluZXJfIl0nOwogIC8vIEFzc2lzdGFudCBtZXNzYWdlIHdyYXBwZXIuIFZlcmlmaWVkIG9uIDIuMS4xNzA6IHRoZSByZW5kZXIgZW1pdHMgZXhhY3RseSBvbmUKICAvLyBgZGF0YS10ZXN0aWQ9ImFzc2lzdGFudC1tZXNzYWdlImAgZGl2IHBlciBhc3Npc3RhbnQgdHVybiwgd2l0aCB0aGUgcmF0aW5nCiAgLy8gd2lkZ2V0IGFuZCBjb250ZW50IGJsb2NrcyBhcyBpdHMgY2hpbGRyZW4uIChUaGUgZWFybGllciBgW2RhdGEtbWVzc2FnZS1yYXRpbmddYAogIC8vIHdhcyBXUk9ORzogdGhhdCBhdHRyaWJ1dGUgc2l0cyBvbiB0aGUgbmVzdGVkIHJhdGluZyBjb250cm9sLCB3aGljaCBpcyBhbHNvIG9ubHkKICAvLyByZW5kZXJlZCBiZWhpbmQgYW4gZXhwZXJpbWVudCthbmFseXRpY3MgZ2F0ZS4pIFJlLXBpbm5lZCBpbiBUYXNrIDYuCiAgdmFyIEFTU0lTVEFOVF9CVUJCTEUgPSAnW2RhdGEtdGVzdGlkPSJhc3Npc3RhbnQtbWVzc2FnZSJdJzsKICB2YXIgTUVTU0FHRVNfQ09OVEFJTkVSID0gJ1tjbGFzcyo9Im1lc3NhZ2VzQ29udGFpbmVyXyJdJzsgLy8gZS5nLiAnW2NsYXNzKj0idGltZWxpbmVfIl0nOyAiIiAtPiBvYnNlcnZlIGRvY3VtZW50LmJvZHkKICAvLyBPcHRpb25hbCBuYXJyb3dpbmcgb25seS4gTVVTVCBiZSBhIHNpbmdsZSB3cmFwcGVyIGFyb3VuZCBBTEwgY29udGVudCBibG9ja3MsCiAgLy8gbm90IGEgcGVyLWJsb2NrIGNsYXNzIChhIHR1cm4gaGFzIG11bHRpcGxlIGJsb2NrcykuICIiIC0+IHVzZSB0aGUgYnViYmxlIGl0c2VsZgogIC8vIChhbHJlYWR5IGFnZ3JlZ2F0ZXMgYWxsIGJsb2Nrczsgc2FuaXRpemVDbG9uZSBpcyB0aGUgY29ycmVjdG5lc3MgZ2F0ZSkuCiAgdmFyIEFTU0lTVEFOVF9DT05URU5UID0gIiI7CiAgdmFyIEZFRURCQUNLX01TID0gMjAwMDsgLy8gaG93IGxvbmcgdGhlIGNoZWNrbWFyayBzaG93cyBhZnRlciBhIHN1Y2Nlc3NmdWwgY29weQoKICAvLyAtLS0tIEhUTUwgLT4gTWFya2Rvd24gKERPTSB3YWxrKSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgLy8gVXNlcyBvbmx5OiBub2RlVHlwZSwgdGFnTmFtZSwgY2hpbGROb2RlcywgdGV4dENvbnRlbnQsIGdldEF0dHJpYnV0ZSwgY2xhc3NOYW1lLgogIGZ1bmN0aW9uIGh0bWxUb01hcmtkb3duKHJvb3QpIHsKICAgIC8vIExvbmdlc3QgcnVuIG9mIGNvbnNlY3V0aXZlIGJhY2t0aWNrcyBpbiBzLCBzbyBhIGNvZGUgZGVsaW1pdGVyL2ZlbmNlIGNhbiBiZQogICAgLy8gY2hvc2VuIGxvbmdlciB0aGFuIGFueXRoaW5nIGluc2lkZSBpdCAoZWxzZSBgYGAgaW4gdGhlIGNvbnRlbnQgY2xvc2VzIGVhcmx5KS4KICAgIGZ1bmN0aW9uIGJhY2t0aWNrUnVuKHMpIHsKICAgICAgdmFyIG1heCA9IDAsIGN1ciA9IDA7CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgcy5sZW5ndGg7IGkrKykgewogICAgICAgIGlmIChzLmNoYXJBdChpKSA9PT0gImAiKSB7IGN1cisrOyBpZiAoY3VyID4gbWF4KSBtYXggPSBjdXI7IH0gZWxzZSBjdXIgPSAwOwogICAgICB9CiAgICAgIHJldHVybiBtYXg7CiAgICB9CiAgICBmdW5jdGlvbiBmZW5jZShzLCBtaW4pIHsgdmFyIG4gPSBiYWNrdGlja1J1bihzKSArIDE7IGlmIChuIDwgbWluKSBuID0gbWluOyByZXR1cm4gbmV3IEFycmF5KG4gKyAxKS5qb2luKCJgIik7IH0KICAgIGZ1bmN0aW9uIGlubGluZShub2RlKSB7CiAgICAgIHZhciBvdXQgPSAiIjsKICAgICAgdmFyIGtpZHMgPSBub2RlLmNoaWxkTm9kZXMgfHwgW107CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwga2lkcy5sZW5ndGg7IGkrKykgewogICAgICAgIHZhciBjID0ga2lkc1tpXTsKICAgICAgICBpZiAoYy5ub2RlVHlwZSA9PT0gMykgeyBvdXQgKz0gYy50ZXh0Q29udGVudCB8fCAiIjsgY29udGludWU7IH0KICAgICAgICBpZiAoYy5ub2RlVHlwZSAhPT0gMSkgY29udGludWU7CiAgICAgICAgdmFyIHRhZyA9IChjLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCk7CiAgICAgICAgaWYgKHRhZyA9PT0gIkJSIikgb3V0ICs9ICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiU1RST05HIiB8fCB0YWcgPT09ICJCIikgb3V0ICs9ICIqKiIgKyBpbmxpbmUoYykgKyAiKioiOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIkVNIiB8fCB0YWcgPT09ICJJIikgb3V0ICs9ICIqIiArIGlubGluZShjKSArICIqIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJERUwiIHx8IHRhZyA9PT0gIlMiKSBvdXQgKz0gIn5+IiArIGlubGluZShjKSArICJ+fiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiQ09ERSIpIHsKICAgICAgICAgIHZhciBjdCA9IGMudGV4dENvbnRlbnQgfHwgIiI7CiAgICAgICAgICB2YXIgZCA9IGZlbmNlKGN0LCAxKTsKICAgICAgICAgIC8vIENvbW1vbk1hcmsgc3RyaXBzIG9uZSBsZWFkaW5nK3RyYWlsaW5nIHNwYWNlLCBzbyBwYWQgd2hlbiBhbiBlZGdlIGlzIGEKICAgICAgICAgIC8vIGJhY2t0aWNrIHRvIGtlZXAgaXQgZnJvbSBtZXJnaW5nIHdpdGggdGhlIGRlbGltaXRlci4KICAgICAgICAgIHZhciBwID0gKGN0LmNoYXJBdCgwKSA9PT0gImAiIHx8IGN0LmNoYXJBdChjdC5sZW5ndGggLSAxKSA9PT0gImAiKSA/ICIgIiA6ICIiOwogICAgICAgICAgb3V0ICs9IGQgKyBwICsgY3QgKyBwICsgZDsKICAgICAgICB9CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiQSIpIHsKICAgICAgICAgIHZhciBocmVmID0gYy5nZXRBdHRyaWJ1dGUgPyBjLmdldEF0dHJpYnV0ZSgiaHJlZiIpIDogbnVsbDsKICAgICAgICAgIHZhciB0ID0gaW5saW5lKGMpOwogICAgICAgICAgb3V0ICs9IGhyZWYgPyAiWyIgKyB0ICsgIl0oIiArIGhyZWYgKyAiKSIgOiB0OwogICAgICAgIH0gZWxzZSBvdXQgKz0gaW5saW5lKGMpOyAvLyB1bmtub3duIGlubGluZSB3cmFwcGVyOiBrZWVwIHRleHQsIGRyb3AgdGFnCiAgICAgIH0KICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIGZ1bmN0aW9uIGxhbmdPZihjb2RlRWwpIHsKICAgICAgdmFyIGNscyA9ICIiOwogICAgICBpZiAoY29kZUVsKSBjbHMgPSAoY29kZUVsLmdldEF0dHJpYnV0ZSAmJiBjb2RlRWwuZ2V0QXR0cmlidXRlKCJjbGFzcyIpKSB8fCBjb2RlRWwuY2xhc3NOYW1lIHx8ICIiOwogICAgICB2YXIgbSA9IC9sYW5ndWFnZS0oW0EtWmEtejAtOSsjLlwtXSspLy5leGVjKGNscyB8fCAiIik7CiAgICAgIHJldHVybiBtID8gbVsxXSA6ICIiOwogICAgfQogICAgZnVuY3Rpb24gZmluZENoaWxkVGFnKG5vZGUsIHRhZykgewogICAgICB2YXIga2lkcyA9IG5vZGUuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgaWYgKGtpZHNbaV0ubm9kZVR5cGUgPT09IDEgJiYgKGtpZHNbaV0udGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKSA9PT0gdGFnKSByZXR1cm4ga2lkc1tpXTsKICAgICAgfQogICAgICByZXR1cm4gbnVsbDsKICAgIH0KICAgIGZ1bmN0aW9uIGxpc3Qobm9kZSwgb3JkZXJlZCwgZGVwdGgpIHsKICAgICAgdmFyIG91dCA9ICIiLCBuID0gMTsKICAgICAgdmFyIGtpZHMgPSBub2RlLmNoaWxkTm9kZXMgfHwgW107CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwga2lkcy5sZW5ndGg7IGkrKykgewogICAgICAgIHZhciBsaSA9IGtpZHNbaV07CiAgICAgICAgaWYgKGxpLm5vZGVUeXBlICE9PSAxIHx8IChsaS50YWdOYW1lIHx8ICIiKS50b1VwcGVyQ2FzZSgpICE9PSAiTEkiKSBjb250aW51ZTsKICAgICAgICB2YXIgbWFya2VyID0gb3JkZXJlZCA/IG4rKyArICIuICIgOiAiLSAiOwogICAgICAgIHZhciBpbmRlbnQgPSBuZXcgQXJyYXkoZGVwdGggKyAxKS5qb2luKCIgICIpOwogICAgICAgIHZhciBsZWFkID0gIiIsIG5lc3RlZCA9ICIiOwogICAgICAgIHZhciBsayA9IGxpLmNoaWxkTm9kZXMgfHwgW107CiAgICAgICAgZm9yICh2YXIgaiA9IDA7IGogPCBsay5sZW5ndGg7IGorKykgewogICAgICAgICAgdmFyIGNoID0gbGtbal07CiAgICAgICAgICB2YXIgY3QgPSBjaC5ub2RlVHlwZSA9PT0gMSA/IChjaC50YWdOYW1lIHx8ICIiKS50b1VwcGVyQ2FzZSgpIDogIiI7CiAgICAgICAgICBpZiAoY3QgPT09ICJVTCIpIG5lc3RlZCArPSBsaXN0KGNoLCBmYWxzZSwgZGVwdGggKyAxKTsKICAgICAgICAgIGVsc2UgaWYgKGN0ID09PSAiT0wiKSBuZXN0ZWQgKz0gbGlzdChjaCwgdHJ1ZSwgZGVwdGggKyAxKTsKICAgICAgICAgIGVsc2UgaWYgKGNoLm5vZGVUeXBlID09PSAzKSBsZWFkICs9IGNoLnRleHRDb250ZW50IHx8ICIiOwogICAgICAgICAgZWxzZSBsZWFkICs9IGlubGluZShjaCk7CiAgICAgICAgfQogICAgICAgIG91dCArPSBpbmRlbnQgKyBtYXJrZXIgKyBsZWFkLnRyaW0oKSArICJcbiIgKyBuZXN0ZWQ7CiAgICAgIH0KICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIGZ1bmN0aW9uIHRhYmxlKG5vZGUpIHsKICAgICAgdmFyIHJvd3MgPSBbXTsKICAgICAgKGZ1bmN0aW9uIGNvbGxlY3QoY29udGFpbmVyKSB7CiAgICAgICAgdmFyIGtpZHMgPSBjb250YWluZXIuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGtpZHMubGVuZ3RoOyBpKyspIHsKICAgICAgICAgIHZhciBjID0ga2lkc1tpXTsKICAgICAgICAgIGlmIChjLm5vZGVUeXBlICE9PSAxKSBjb250aW51ZTsKICAgICAgICAgIHZhciB0ID0gKGMudGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKTsKICAgICAgICAgIGlmICh0ID09PSAiVEhFQUQiIHx8IHQgPT09ICJUQk9EWSIgfHwgdCA9PT0gIlRGT09UIikgY29sbGVjdChjKTsKICAgICAgICAgIGVsc2UgaWYgKHQgPT09ICJUUiIpIHsKICAgICAgICAgICAgdmFyIGNlbGxzID0gW10sIGNjID0gYy5jaGlsZE5vZGVzIHx8IFtdOwogICAgICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IGNjLmxlbmd0aDsgaisrKSB7CiAgICAgICAgICAgICAgdmFyIGQgPSBjY1tqXTsKICAgICAgICAgICAgICBpZiAoZC5ub2RlVHlwZSAhPT0gMSkgY29udGludWU7CiAgICAgICAgICAgICAgdmFyIGR0ID0gKGQudGFnTmFtZSB8fCAiIikudG9VcHBlckNhc2UoKTsKICAgICAgICAgICAgICBpZiAoZHQgPT09ICJUSCIgfHwgZHQgPT09ICJURCIpIGNlbGxzLnB1c2goaW5saW5lKGQpLnRyaW0oKSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgcm93cy5wdXNoKGNlbGxzKTsKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0pKG5vZGUpOwogICAgICBpZiAoIXJvd3MubGVuZ3RoKSByZXR1cm4gIiI7CiAgICAgIHZhciBoZWFkID0gcm93c1swXSwgYm9keSA9IHJvd3Muc2xpY2UoMSk7CiAgICAgIHZhciBzZXAgPSBoZWFkLm1hcChmdW5jdGlvbiAoKSB7IHJldHVybiAiLS0tIjsgfSk7CiAgICAgIHZhciBvdXQgPSAifCAiICsgaGVhZC5qb2luKCIgfCAiKSArICIgfFxufCAiICsgc2VwLmpvaW4oIiB8ICIpICsgIiB8XG4iOwogICAgICBmb3IgKHZhciBrID0gMDsgayA8IGJvZHkubGVuZ3RoOyBrKyspIG91dCArPSAifCAiICsgYm9keVtrXS5qb2luKCIgfCAiKSArICIgfFxuIjsKICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIGZ1bmN0aW9uIGJsb2NrKG5vZGUpIHsKICAgICAgdmFyIG91dCA9ICIiOwogICAgICB2YXIga2lkcyA9IG5vZGUuY2hpbGROb2RlcyB8fCBbXTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBraWRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgdmFyIGMgPSBraWRzW2ldOwogICAgICAgIGlmIChjLm5vZGVUeXBlID09PSAzKSB7IGlmICgoYy50ZXh0Q29udGVudCB8fCAiIikudHJpbSgpKSBvdXQgKz0gYy50ZXh0Q29udGVudDsgY29udGludWU7IH0KICAgICAgICBpZiAoYy5ub2RlVHlwZSAhPT0gMSkgY29udGludWU7CiAgICAgICAgdmFyIHRhZyA9IChjLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCk7CiAgICAgICAgaWYgKC9eSFsxLTZdJC8udGVzdCh0YWcpKSBvdXQgKz0gbmV3IEFycmF5KCt0YWdbMV0gKyAxKS5qb2luKCIjIikgKyAiICIgKyBpbmxpbmUoYykudHJpbSgpICsgIlxuXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIlAiKSBvdXQgKz0gaW5saW5lKGMpLnRyaW0oKSArICJcblxuIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJVTCIpIG91dCArPSBsaXN0KGMsIGZhbHNlLCAwKSArICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiT0wiKSBvdXQgKz0gbGlzdChjLCB0cnVlLCAwKSArICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiUFJFIikgewogICAgICAgICAgdmFyIGNvZGUgPSBmaW5kQ2hpbGRUYWcoYywgIkNPREUiKTsKICAgICAgICAgIHZhciBsYW5nID0gbGFuZ09mKGNvZGUgfHwgYyk7CiAgICAgICAgICB2YXIgYm9keSA9IChjb2RlIHx8IGMpLnRleHRDb250ZW50IHx8ICIiOwogICAgICAgICAgdmFyIGYgPSBmZW5jZShib2R5LCAzKTsKICAgICAgICAgIG91dCArPSBmICsgbGFuZyArICJcbiIgKyBib2R5LnJlcGxhY2UoL1xuJC8sICIiKSArICJcbiIgKyBmICsgIlxuXG4iOwogICAgICAgIH0gZWxzZSBpZiAodGFnID09PSAiQkxPQ0tRVU9URSIpIHsKICAgICAgICAgIHZhciBpbm5lciA9IGJsb2NrKGMpLnRyaW0oKS5zcGxpdCgiXG4iKS5tYXAoZnVuY3Rpb24gKGwpIHsgcmV0dXJuICI+ICIgKyBsOyB9KS5qb2luKCJcbiIpOwogICAgICAgICAgb3V0ICs9IGlubmVyICsgIlxuXG4iOwogICAgICAgIH0gZWxzZSBpZiAodGFnID09PSAiREVUQUlMUyIpIG91dCArPSBibG9jayhjKS50cmltKCkgKyAiXG5cbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiU1VNTUFSWSIpIG91dCArPSBpbmxpbmUoYykudHJpbSgpICsgIlxuXG4iOwogICAgICAgIGVsc2UgaWYgKHRhZyA9PT0gIkhSIikgb3V0ICs9ICItLS1cblxuIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJUQUJMRSIpIG91dCArPSB0YWJsZShjKSArICJcbiI7CiAgICAgICAgZWxzZSBpZiAodGFnID09PSAiQlIiKSBvdXQgKz0gIlxuIjsKICAgICAgICBlbHNlIGlmICh0YWcgPT09ICJTVFJPTkciIHx8IHRhZyA9PT0gIkIiIHx8IHRhZyA9PT0gIkVNIiB8fCB0YWcgPT09ICJJIiB8fAogICAgICAgICAgICAgICAgIHRhZyA9PT0gIkEiIHx8IHRhZyA9PT0gIkNPREUiIHx8IHRhZyA9PT0gIkRFTCIgfHwgdGFnID09PSAiUyIpCiAgICAgICAgICBvdXQgKz0gaW5saW5lKGMpICsgIlxuXG4iOwogICAgICAgIGVsc2Ugb3V0ICs9IGJsb2NrKGMpOyAvLyB1bmtub3duIHdyYXBwZXI6IHJlY3Vyc2UgKGRyb3AgdGFnLCBrZWVwIGNvbnRlbnQpCiAgICAgIH0KICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIC8vIGJsb2NrKCkgZGlzcGF0Y2hlcyBvbiBlYWNoIENISUxEJ3MgdGFnLCB0cmVhdGluZyB0aGUgcGFzc2VkIG5vZGUgYXMgYSBwbGFpbgogICAgLy8gY29udGFpbmVyLiBXcmFwIHJvb3QgaW4gYSBvbmUtb2ZmIGNvbnRhaW5lciBzbyByb290J3MgT1dOIHRhZyBpcyBkaXNwYXRjaGVkCiAgICAvLyB0b286IGNhbGxlcnMgcGFzcyBlaXRoZXIgdGhlIGJ1YmJsZSBjb250YWluZXIgKGl0cyBibG9jayBjaGlsZHJlbiByZW5kZXIpIG9yCiAgICAvLyBhIHNpbmdsZSBibG9jayBlbGVtZW50IGxpa2UgPHByZT4vPHVsPi88dGFibGU+IChub3cgaGFuZGxlZCwgbm90IGZsYXR0ZW5lZCkuCiAgICByZXR1cm4gYmxvY2soeyBjaGlsZE5vZGVzOiBbcm9vdF0gfSkucmVwbGFjZSgvXG57Myx9L2csICJcblxuIikudHJpbSgpOwogIH0KCiAgLy8gLS0tLSBwdXJlIGhlbHBlcnMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogIGZ1bmN0aW9uIGhhc1ByZWZpeChub2RlLCBwcmVmaXgpIHsKICAgIGlmIChub2RlLm5vZGVUeXBlICE9PSAxIHx8IHR5cGVvZiBub2RlLmNsYXNzTmFtZSAhPT0gInN0cmluZyIpIHJldHVybiBmYWxzZTsKICAgIHZhciBwYXJ0cyA9IG5vZGUuY2xhc3NOYW1lLnNwbGl0KC9ccysvKTsKICAgIGZvciAodmFyIGkgPSAwOyBpIDwgcGFydHMubGVuZ3RoOyBpKyspIGlmIChwYXJ0c1tpXS5pbmRleE9mKHByZWZpeCkgPT09IDApIHJldHVybiB0cnVlOwogICAgcmV0dXJuIGZhbHNlOwogIH0KCiAgLy8gQ2xhc3MtcHJlZml4IGhvb2tzIGZvciBub24tY29udGVudCBjaHJvbWUgdGhhdCByZW5kZXJzICppbnNpZGUqIGFuIGFzc2lzdGFudAogIC8vIGJ1YmJsZSAodmVyaWZpZWQgb24gMi4xLjE3MDsgVGFzayA2IHJlLXBpbnMgdGhlc2UpLiBUb29sIGJsb2NrcyBhcmUgZXhjbHVkZWQKICAvLyBmcm9tIG1lc3NhZ2UgY29weTsgdGhpbmtpbmcgc3VtbWFyaWVzIGFyZSB2aXNpYmxlIGNvbnRlbnQgYW5kIG11c3QgcmVtYWluCiAgLy8gY29weWFibGUuIHVua25vd25Db250ZW50XyBpcyB0aGUgcmVuZGVyZXIncyBmYWxsYmFjayBmb3IgdW5yZWNvZ25pemVkIGJsb2NrCiAgLy8gdHlwZXMsIHNvIHN0cmlwcGluZyBpdCBtYWtlcyBhICpmdXR1cmUqIGJsb2NrIHR5cGUgZmFpbCBzYWZlIHRvIGV4Y2x1ZGVkIHJhdGhlcgogIC8vIHRoYW4gbGVha2luZyAiVW5zdXBwb3J0ZWQgY29udGVudCIgaW50byB0aGUgY29weS4gUmUtcGluIGlmIGEgcHJlZml4IG1vdmVzLgogIHZhciBDSFJPTUVfUFJFRklYRVMgPSBbInRvb2xVc2VfIiwgInRvb2xSZXN1bHRfIiwgInRvb2xSZWZlcmVuY2VfIiwgInVua25vd25Db250ZW50XyJdOwoKICAvLyBUcnVlIGZvciBhbnkgbm9kZSB0aGF0IG11c3QgbmV2ZXIgYXBwZWFyIGluIGNvcGllZCBvdXRwdXQ6IG91ciBvd24gY29udHJvbHMsCiAgLy8gdGhlIHJhdGluZyB3aWRnZXQgKGBkYXRhLW1lc3NhZ2UtcmF0aW5nYCArIGl0cyAiVGhhbmtzIGZvciB5b3VyIGZlZWRiYWNrIgogIC8vIHRleHQpLCBhbnkgYnV0dG9uIChjb3B5LWNvZGUgY2hyb21lKSwgYW5kIHRoZSBleGNsdWRlZCBjb250ZW50IGJsb2NrcyBhYm92ZS4KICBmdW5jdGlvbiBpc0Nocm9tZShub2RlKSB7CiAgICBpZiAobm9kZS5ub2RlVHlwZSAhPT0gMSkgcmV0dXJuIGZhbHNlOwogICAgaWYgKChub2RlLnRhZ05hbWUgfHwgIiIpLnRvVXBwZXJDYXNlKCkgPT09ICJCVVRUT04iKSByZXR1cm4gdHJ1ZTsKICAgIGlmIChub2RlLmdldEF0dHJpYnV0ZSAmJiBub2RlLmdldEF0dHJpYnV0ZSgiZGF0YS1tZXNzYWdlLXJhdGluZyIpICE9PSBudWxsKSByZXR1cm4gdHJ1ZTsKICAgIGlmIChoYXNQcmVmaXgobm9kZSwgQ09OVFJPTF9QUkVGSVgpKSByZXR1cm4gdHJ1ZTsKICAgIGZvciAodmFyIGkgPSAwOyBpIDwgQ0hST01FX1BSRUZJWEVTLmxlbmd0aDsgaSsrKSBpZiAoaGFzUHJlZml4KG5vZGUsIENIUk9NRV9QUkVGSVhFU1tpXSkpIHJldHVybiB0cnVlOwogICAgcmV0dXJuIGZhbHNlOwogIH0KCiAgLy8gRGVlcC1jbG9uZSBgY29udGVudE5vZGVgLCB0aGVuIHN0cmlwIGV2ZXJ5IGNocm9tZSBub2RlIHNvIGNvcGllZCBvdXRwdXQgaXMgdGhlCiAgLy8gbWVzc2FnZSdzIHRleHQgY29udGVudCBvbmx5LiBUaGlzIGlzIGEgQ09SUkVDVE5FU1MgR0FURSwgbm90IGNvc21ldGljOiB0aGUKICAvLyBkZWZhdWx0IGNvbnRlbnQgbm9kZSBpcyB0aGUgd2hvbGUgYnViYmxlIChhbGwgY29udGVudC1ibG9jayBzaWJsaW5ncywgc28gbXVsdGktCiAgLy8gYmxvY2sgYXNzaXN0YW50IHR1cm5zIGFyZSBjYXB0dXJlZCksIGFuZCB0aGlzIHN0cmlwLWxpc3QgaXMgdGhlIG9ubHkgdGhpbmcKICAvLyBrZWVwaW5nIHRoZSByYXRpbmcgd2lkZ2V0IGFuZCBleGNsdWRlZCB0b29sL2ZhbGxiYWNrIGJsb2NrcyBvdXQgb2YgdGhlIGNvcHkuCiAgZnVuY3Rpb24gc2FuaXRpemVDbG9uZShjb250ZW50Tm9kZSkgewogICAgdmFyIGNsb25lID0gY29udGVudE5vZGUuY2xvbmVOb2RlKHRydWUpOwogICAgKGZ1bmN0aW9uIHN0cmlwKG5vZGUpIHsKICAgICAgdmFyIGtpZHMgPSBBcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChub2RlLmNoaWxkTm9kZXMgfHwgW10pOwogICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGtpZHMubGVuZ3RoOyBpKyspIHsKICAgICAgICB2YXIgYyA9IGtpZHNbaV07CiAgICAgICAgaWYgKGMubm9kZVR5cGUgPT09IDEgJiYgaXNDaHJvbWUoYykpIHsgbm9kZS5yZW1vdmVDaGlsZChjKTsgY29udGludWU7IH0KICAgICAgICBpZiAoYy5ub2RlVHlwZSA9PT0gMSkgc3RyaXAoYyk7CiAgICAgIH0KICAgIH0pKGNsb25lKTsKICAgIHJldHVybiBjbG9uZTsKICB9CgogIGZ1bmN0aW9uIGhhc0NvcHlhYmxlQ29udGVudChjb250ZW50Tm9kZSwgcm9sZSkgewogICAgZnVuY3Rpb24gd2Fsayhub2RlKSB7CiAgICAgIGlmICghbm9kZSkgcmV0dXJuIGZhbHNlOwogICAgICBpZiAobm9kZS5ub2RlVHlwZSA9PT0gMykgcmV0dXJuICEhKG5vZGUudGV4dENvbnRlbnQgfHwgIiIpLnRyaW0oKTsKICAgICAgaWYgKG5vZGUubm9kZVR5cGUgIT09IDEpIHJldHVybiBmYWxzZTsKICAgICAgaWYgKGlzQ2hyb21lKG5vZGUpKSByZXR1cm4gZmFsc2U7CiAgICAgIHZhciBraWRzID0gbm9kZS5jaGlsZE5vZGVzIHx8IFtdOwogICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGtpZHMubGVuZ3RoOyBpKyspIGlmICh3YWxrKGtpZHNbaV0pKSByZXR1cm4gdHJ1ZTsKICAgICAgcmV0dXJuIGZhbHNlOwogICAgfQogICAgcmV0dXJuIHdhbGsoY29udGVudE5vZGUpOwogIH0KCiAgZnVuY3Rpb24gY2xhc3NpZnlCdWJibGUobm9kZSkgewogICAgaWYgKG5vZGUubm9kZVR5cGUgIT09IDEpIHJldHVybiBudWxsOwogICAgaWYgKGhhc1ByZWZpeChub2RlLCAidXNlck1lc3NhZ2VDb250YWluZXJfIikpIHJldHVybiAidXNlciI7CiAgICBpZiAobm9kZS5nZXRBdHRyaWJ1dGUgJiYgbm9kZS5nZXRBdHRyaWJ1dGUoImRhdGEtdGVzdGlkIikgPT09ICJhc3Npc3RhbnQtbWVzc2FnZSIpIHJldHVybiAiYXNzaXN0YW50IjsKICAgIHJldHVybiBudWxsOwogIH0KCiAgLy8gQnVpbGQgdGhlIHdob2xlLWNvbnZlcnNhdGlvbiBtYXJrZG93biBmcm9tIGFuIG9yZGVyZWQgbGlzdCBvZiBidWJibGVzLgogIC8vIGBjb250ZW50T2YoYnViYmxlKWAgcmVzb2x2ZXMgdGhlIGNvbnRlbnQgbm9kZSAoZGVmYXVsdDogdGhlIGJ1YmJsZSBpdHNlbGYsIHNvCiAgLy8gZXZlcnkgY29udGVudCBibG9jayBpcyBpbmNsdWRlZDsgc2FuaXRpemVDbG9uZSBkcm9wcyBjaHJvbWUpOyBhIGRlZmF1bHQgaXMKICAvLyBwcm92aWRlZCBmb3IgdGVzdHMuCiAgZnVuY3Rpb24gY29udmVyc2F0aW9uVG9NYXJrZG93bihidWJibGVzLCBjb250ZW50T2YpIHsKICAgIGNvbnRlbnRPZiA9IGNvbnRlbnRPZiB8fCBmdW5jdGlvbiAoYikgeyByZXR1cm4gYjsgfTsKICAgIHZhciBwYXJ0cyA9IFtdOwogICAgZm9yICh2YXIgaSA9IDA7IGkgPCBidWJibGVzLmxlbmd0aDsgaSsrKSB7CiAgICAgIHZhciByb2xlID0gY2xhc3NpZnlCdWJibGUoYnViYmxlc1tpXSk7CiAgICAgIGlmICghcm9sZSkgY29udGludWU7CiAgICAgIHZhciBjbGVhbiA9IHNhbml0aXplQ2xvbmUoY29udGVudE9mKGJ1YmJsZXNbaV0pKTsKICAgICAgdmFyIGJvZHkgPSByb2xlID09PSAiYXNzaXN0YW50IiA/IGh0bWxUb01hcmtkb3duKGNsZWFuKSA6IChjbGVhbi50ZXh0Q29udGVudCB8fCAiIikudHJpbSgpOwogICAgICBpZiAoIWJvZHkpIGNvbnRpbnVlOwogICAgICBwYXJ0cy5wdXNoKChyb2xlID09PSAidXNlciIgPyAiIyMgVXNlciIgOiAiIyMgQXNzaXN0YW50IikgKyAiXG5cbiIgKyBib2R5KTsKICAgIH0KICAgIHJldHVybiBwYXJ0cy5qb2luKCJcblxuIikgKyAocGFydHMubGVuZ3RoID8gIlxuIiA6ICIiKTsKICB9CgogIC8vIC0tLS0gZXhwb3J0cyAobm9kZSB0ZXN0cykgLyBib290IChyZWFsIHdlYnZpZXcpIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KICBpZiAodHlwZW9mIGRvY3VtZW50ICE9PSAidW5kZWZpbmVkIikgewogICAgYm9vdCgpOwogIH0gZWxzZSBpZiAodHlwZW9mIG1vZHVsZSAhPT0gInVuZGVmaW5lZCIgJiYgbW9kdWxlLmV4cG9ydHMpIHsKICAgIG1vZHVsZS5leHBvcnRzID0geyBodG1sVG9NYXJrZG93bjogaHRtbFRvTWFya2Rvd24sIHNhbml0aXplQ2xvbmU6IHNhbml0aXplQ2xvbmUsCiAgICAgICAgICAgICAgICAgICAgICAgY2xhc3NpZnlCdWJibGU6IGNsYXNzaWZ5QnViYmxlLCBjb252ZXJzYXRpb25Ub01hcmtkb3duOiBjb252ZXJzYXRpb25Ub01hcmtkb3duLAogICAgICAgICAgICAgICAgICAgICAgIGhhc0NvcHlhYmxlQ29udGVudDogaGFzQ29weWFibGVDb250ZW50LCBjb3B5VGV4dDogY29weVRleHQgfTsKICB9CgogIC8vIC0tLS0gbGl2ZS13ZWJ2aWV3IHdpcmluZyAocnVucyBvbmx5IHdoZW4gYSBkb2N1bWVudCBleGlzdHMpIC0tLS0tLS0tLS0tLS0tLS0KICBmdW5jdGlvbiBxcyhub2RlLCBzZWwpIHsgdHJ5IHsgcmV0dXJuIHNlbCAmJiBub2RlLnF1ZXJ5U2VsZWN0b3IgPyBub2RlLnF1ZXJ5U2VsZWN0b3Ioc2VsKSA6IG51bGw7IH0gY2F0Y2ggKF8pIHsgcmV0dXJuIG51bGw7IH0gfQogIGZ1bmN0aW9uIHFzYShzZWwpIHsgdHJ5IHsgcmV0dXJuIEFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoc2VsKSk7IH0gY2F0Y2ggKF8pIHsgcmV0dXJuIFtdOyB9IH0KCiAgLy8gVGhlIGNvbnRlbnQgbm9kZSB0byBjb252ZXJ0L2NvcHk6IHRoZSBvcHRpb25hbCBBU1NJU1RBTlRfQ09OVEVOVCB3cmFwcGVyIGlmCiAgLy8gcGlubmVkIGFuZCBwcmVzZW50LCBlbHNlIHRoZSBidWJibGUgaXRzZWxmLiBUaGUgYnViYmxlIGFscmVhZHkgY29udGFpbnMgZXZlcnkKICAvLyBjb250ZW50LWJsb2NrIHNpYmxpbmcgb2YgYSBtdWx0aS1ibG9jayB0dXJuLCBhbmQgc2FuaXRpemVDbG9uZSBzdHJpcHMgdGhlCiAgLy8gY2hyb21lIChyYXRpbmcgd2lkZ2V0LCB0b29sL3Vua25vd24gYmxvY2tzLCBidXR0b25zLCBvdXIgY29udHJvbHMpCiAgLy8gZWl0aGVyIHdheSAtLSBzbyB0aGlzIGlzIGEgbmFycm93aW5nLCBuZXZlciB0aGUgdGhpbmcgdGhhdCBndWFyYW50ZWVzCiAgLy8gY29ycmVjdG5lc3MuCiAgZnVuY3Rpb24gY29udGVudE5vZGVPZihidWJibGUsIHJvbGUpIHsKICAgIGlmIChyb2xlID09PSAiYXNzaXN0YW50IiAmJiBBU1NJU1RBTlRfQ09OVEVOVCkgewogICAgICB2YXIgbiA9IHFzKGJ1YmJsZSwgQVNTSVNUQU5UX0NPTlRFTlQpOwogICAgICBpZiAobikgcmV0dXJuIG47CiAgICB9CiAgICByZXR1cm4gYnViYmxlOwogIH0KCiAgLy8gQ29weSBgc2AgdmlhIGEgc3luY2hyb25vdXMgZXhlY0NvbW1hbmQoImNvcHkiKSBvbiBhbiBvZmYtc2NyZWVuIHRleHRhcmVhLCBhbmQKICAvLyByZXBvcnQgd2hldGhlciBpdCBhY3R1YWxseSBoYXBwZW5lZC4gRG9uZSBmaXJzdCAoYW5kIHN5bmNocm9ub3VzbHkpIGJlY2F1c2UgaXQKICAvLyBydW5zIGluc2lkZSB0aGUgY2xpY2sgZ2VzdHVyZSBhbmQgd29ya3Mgd2hldGhlciBvciBub3QgdGhlIHBhZ2UgaXMgYSBzZWN1cmUKICAvLyBjb250ZXh0IC0tIHNvIGl0IGNvdmVycyByZW1vdGUgLyBjb2RlLXNlcnZlciwgd2hlcmUgdGhlIGFzeW5jIENsaXBib2FyZCBBUEkgaXMKICAvLyBzaW1wbHkgYWJzZW50LiBSZXN0b3JlcyB0aGUgcHJpb3Igc2VsZWN0aW9uL2ZvY3VzIHNvIGl0IGlzIGludmlzaWJsZS4KICBmdW5jdGlvbiBleGVjQ29weShzKSB7CiAgICB0cnkgewogICAgICBpZiAodHlwZW9mIGRvY3VtZW50ID09PSAidW5kZWZpbmVkIiB8fCAhZG9jdW1lbnQuY3JlYXRlRWxlbWVudCkgcmV0dXJuIGZhbHNlOwogICAgICB2YXIgcHJldiA9IGRvY3VtZW50LmFjdGl2ZUVsZW1lbnQgfHwgbnVsbDsKICAgICAgdmFyIHNlbCA9IGRvY3VtZW50LmdldFNlbGVjdGlvbiA/IGRvY3VtZW50LmdldFNlbGVjdGlvbigpIDogbnVsbDsKICAgICAgdmFyIHNhdmVkID0gKHNlbCAmJiBzZWwucmFuZ2VDb3VudCkgPyBzZWwuZ2V0UmFuZ2VBdCgwKSA6IG51bGw7CiAgICAgIHZhciB0YSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInRleHRhcmVhIik7CiAgICAgIHRhLnZhbHVlID0gczsKICAgICAgdGEuc2V0QXR0cmlidXRlKCJyZWFkb25seSIsICIiKTsKICAgICAgdGEuc3R5bGUucG9zaXRpb24gPSAiZml4ZWQiOwogICAgICB0YS5zdHlsZS50b3AgPSAiLTEwMDBweCI7CiAgICAgIHRhLnN0eWxlLmxlZnQgPSAiMCI7CiAgICAgIHRhLnN0eWxlLm9wYWNpdHkgPSAiMCI7CiAgICAgIChkb2N1bWVudC5ib2R5IHx8IGRvY3VtZW50LmRvY3VtZW50RWxlbWVudCkuYXBwZW5kQ2hpbGQodGEpOwogICAgICB0YS5mb2N1cygpOwogICAgICB0YS5zZWxlY3QoKTsKICAgICAgdmFyIG9rID0gZmFsc2U7CiAgICAgIHRyeSB7IG9rID0gZG9jdW1lbnQuZXhlY0NvbW1hbmQoImNvcHkiKTsgfSBjYXRjaCAoXykgeyBvayA9IGZhbHNlOyB9CiAgICAgIGlmICh0YS5wYXJlbnROb2RlKSB0YS5wYXJlbnROb2RlLnJlbW92ZUNoaWxkKHRhKTsKICAgICAgaWYgKHNhdmVkICYmIHNlbCkgeyB0cnkgeyBzZWwucmVtb3ZlQWxsUmFuZ2VzKCk7IHNlbC5hZGRSYW5nZShzYXZlZCk7IH0gY2F0Y2ggKF8pIHt9IH0KICAgICAgaWYgKHByZXYgJiYgcHJldi5mb2N1cykgeyB0cnkgeyBwcmV2LmZvY3VzKCk7IH0gY2F0Y2ggKF8pIHt9IH0KICAgICAgcmV0dXJuICEhb2s7CiAgICB9IGNhdGNoIChfKSB7IHJldHVybiBmYWxzZTsgfQogIH0KCiAgLy8gQ29weSBgdGV4dGAgYW5kIHJlc29sdmUgdG8gd2hldGhlciB0aGUgY29weSBBQ1RVQUxMWSBoYXBwZW5lZCwgc28gY2FsbGVycyBvbmx5CiAgLy8gc2hvdyBzdWNjZXNzIG9uIGEgcmVhbCBjb3B5IC0tIG5ldmVyIGEgZmFsc2UgImNvcGllZCIgKHRoZSBvcmlnaW5hbCBidWc6CiAgLy8gbmF2aWdhdG9yLmNsaXBib2FyZCB3YXMgdW5kZWZpbmVkIGluIHRoZSB3ZWJ2aWV3LCB0aGUgY29kZSBmZWxsIHRocm91Z2ggdG8KICAvLyBQcm9taXNlLnJlc29sdmUoKSwgYW5kIHRoZSBVSSBjbGFpbWVkIHN1Y2Nlc3Mgd2hpbGUgbm90aGluZyB3YXMgd3JpdHRlbikuIEVtcHR5CiAgLy8gdGV4dCBpcyBhIG5vbi1jb3B5IC0+IGZhbHNlLiBleGVjQ29tbWFuZCBmaXJzdCAoZ2VzdHVyZS1zYWZlLCBzZWN1cmUtY29udGV4dC0KICAvLyBpbmRlcGVuZGVudCk7IHRoZSBhc3luYyBDbGlwYm9hcmQgQVBJIGlzIHRoZSBmYWxsYmFjay4gTmV2ZXIgdGhyb3dzLgogIGZ1bmN0aW9uIGNvcHlUZXh0KHRleHQpIHsKICAgIHZhciBzID0gKHRleHQgPT0gbnVsbCkgPyAiIiA6IFN0cmluZyh0ZXh0KTsKICAgIGlmICghcykgcmV0dXJuIFByb21pc2UucmVzb2x2ZShmYWxzZSk7CiAgICBpZiAoZXhlY0NvcHkocykpIHJldHVybiBQcm9taXNlLnJlc29sdmUodHJ1ZSk7CiAgICB0cnkgewogICAgICBpZiAodHlwZW9mIG5hdmlnYXRvciAhPT0gInVuZGVmaW5lZCIgJiYgbmF2aWdhdG9yLmNsaXBib2FyZCAmJiBuYXZpZ2F0b3IuY2xpcGJvYXJkLndyaXRlVGV4dCkgewogICAgICAgIHJldHVybiBuYXZpZ2F0b3IuY2xpcGJvYXJkLndyaXRlVGV4dChzKS50aGVuKAogICAgICAgICAgZnVuY3Rpb24gKCkgeyByZXR1cm4gdHJ1ZTsgfSwKICAgICAgICAgIGZ1bmN0aW9uICgpIHsgcmV0dXJuIGZhbHNlOyB9CiAgICAgICAgKTsKICAgICAgfQogICAgfSBjYXRjaCAoXykge30KICAgIHJldHVybiBQcm9taXNlLnJlc29sdmUoZmFsc2UpOwogIH0KCiAgZnVuY3Rpb24gYnViYmxlTWFya2Rvd24oYnViYmxlLCByb2xlKSB7CiAgICB2YXIgY2xlYW4gPSBzYW5pdGl6ZUNsb25lKGNvbnRlbnROb2RlT2YoYnViYmxlLCByb2xlKSk7CiAgICByZXR1cm4gcm9sZSA9PT0gImFzc2lzdGFudCIgPyBodG1sVG9NYXJrZG93bihjbGVhbikgOiAoY2xlYW4udGV4dENvbnRlbnQgfHwgIiIpLnRyaW0oKTsKICB9CgogIC8vIElubGluZSBTVkcgaWNvbnMgKGN1cnJlbnRDb2xvciwgfjE0cHgpLiBTZXQgdmlhIGlubmVySFRNTCBvbiBvdXIgb3duIGJ1dHRvbnMKICAvLyBvbmx5OyB0aGUgbWFya3VwIG5ldmVyIHJlYWNoZXMgY29waWVkIGNvbnRlbnQgKHNhbml0aXplQ2xvbmUgZHJvcHMgb3VyIG5vZGVzKS4KICB2YXIgSUNPTl9DT1BZID0gJzxzdmcgd2lkdGg9IjE0IiBoZWlnaHQ9IjE0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgYXJpYS1oaWRkZW49InRydWUiPjxyZWN0IHg9IjkiIHk9IjkiIHdpZHRoPSIxMyIgaGVpZ2h0PSIxMyIgcng9IjIiIHJ5PSIyIj48L3JlY3Q+PHBhdGggZD0iTTUgMTVINGEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg5YTIgMiAwIDAgMSAyIDJ2MSI+PC9wYXRoPjwvc3ZnPic7CiAgdmFyIElDT05fQ0hFQ0sgPSAnPHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMi41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGFyaWEtaGlkZGVuPSJ0cnVlIj48cG9seWxpbmUgcG9pbnRzPSIyMCA2IDkgMTcgNCAxMiI+PC9wb2x5bGluZT48L3N2Zz4nOwoKICAvLyBGbGlwIHRoZSBidXR0b24gdG8gYSBjaGVja21hcmsgZm9yIEZFRURCQUNLX01TLCB0aGVuIHJlc3RvcmUuIElkZW1wb3RlbnQgYWNyb3NzCiAgLy8gcmFwaWQgY2xpY2tzIChhbnkgcGVuZGluZyByZXN0b3JlIGlzIGNsZWFyZWQgZmlyc3QpLgogIGZ1bmN0aW9uIHNob3dDb3BpZWQoYnRuKSB7CiAgICB0cnkgewogICAgICBpZiAoYnRuLl9fY2NUaW1lcikgY2xlYXJUaW1lb3V0KGJ0bi5fX2NjVGltZXIpOwogICAgICBidG4uY2xhc3NMaXN0LmFkZChDT05UUk9MX1BSRUZJWCArICItb2siKTsKICAgICAgYnRuLmlubmVySFRNTCA9IElDT05fQ0hFQ0s7CiAgICAgIGJ0bi5fX2NjVGltZXIgPSBzZXRUaW1lb3V0KGZ1bmN0aW9uICgpIHsKICAgICAgICB0cnkgeyBidG4uY2xhc3NMaXN0LnJlbW92ZShDT05UUk9MX1BSRUZJWCArICItb2siKTsgYnRuLmlubmVySFRNTCA9IElDT05fQ09QWTsgfSBjYXRjaCAoXykge30KICAgICAgICBidG4uX19jY1RpbWVyID0gbnVsbDsKICAgICAgfSwgRkVFREJBQ0tfTVMpOwogICAgfSBjYXRjaCAoXykge30KICB9CgogIC8vIEJ1aWxkIGEgc2luZ2xlIGNvbnRyb2w6IG9uZSBjbGlwYm9hcmQtaWNvbiBidXR0b24uIGBvbkNvcHkoKWAgaXMgaW52b2tlZAogIC8vIHN5bmNocm9ub3VzbHkgb24gY2xpY2sgKHNvIHRoZSBjb3B5IHN0YXlzIGluc2lkZSB0aGUgdXNlciBnZXN0dXJlKSBhbmQgbXVzdAogIC8vIHJldHVybiBhIFByb21pc2U8Ym9vbGVhbj47IHRoZSBjaGVja21hcmsgc2hvd3Mgb25seSB3aGVuIGl0IHJlc29sdmVzIHRydWUuIEFsbAogIC8vIG5vZGVzIGNhcnJ5IHRoZSBDT05UUk9MX1BSRUZJWCBjbGFzcyBzbyBzYW5pdGl6ZUNsb25lIHN0cmlwcyB0aGVtIGZyb20gY29waWVzLgogIGZ1bmN0aW9uIGJ1aWxkQ29udHJvbChvbkNvcHksIHRpdGxlKSB7CiAgICB2YXIgd3JhcCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInNwYW4iKTsKICAgIHdyYXAuY2xhc3NOYW1lID0gQ09OVFJPTF9QUkVGSVg7CiAgICB2YXIgYnRuID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiYnV0dG9uIik7CiAgICBidG4udHlwZSA9ICJidXR0b24iOwogICAgYnRuLmNsYXNzTmFtZSA9IENPTlRST0xfUFJFRklYICsgIi1idG4iOwogICAgYnRuLnRpdGxlID0gdGl0bGUgfHwgIkNvcHkgYXMgTWFya2Rvd24iOwogICAgYnRuLnNldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbCIsIGJ0bi50aXRsZSk7CiAgICBidG4uaW5uZXJIVE1MID0gSUNPTl9DT1BZOwogICAgdmFyIGJ1c3kgPSBmYWxzZTsKICAgIGJ0bi5hZGRFdmVudExpc3RlbmVyKCJjbGljayIsIGZ1bmN0aW9uIChlKSB7CiAgICAgIGUuc3RvcFByb3BhZ2F0aW9uKCk7CiAgICAgIGlmIChidXN5KSByZXR1cm47CiAgICAgIGJ1c3kgPSB0cnVlOwogICAgICB2YXIgcDsKICAgICAgdHJ5IHsgcCA9IG9uQ29weSgpOyB9IGNhdGNoIChfKSB7IHAgPSBmYWxzZTsgfQogICAgICBQcm9taXNlLnJlc29sdmUocCkudGhlbigKICAgICAgICBmdW5jdGlvbiAob2spIHsgYnVzeSA9IGZhbHNlOyBpZiAob2spIHNob3dDb3BpZWQoYnRuKTsgfSwKICAgICAgICBmdW5jdGlvbiAoKSB7IGJ1c3kgPSBmYWxzZTsgfQogICAgICApOwogICAgfSk7CiAgICB3cmFwLmFwcGVuZENoaWxkKGJ0bik7CiAgICByZXR1cm4gd3JhcDsKICB9CgogIGZ1bmN0aW9uIGRlY29yYXRlKGJ1YmJsZSkgewogICAgdHJ5IHsKICAgICAgdmFyIHJvbGUgPSBjbGFzc2lmeUJ1YmJsZShidWJibGUpOwogICAgICBpZiAoIXJvbGUpIHJldHVybjsKICAgICAgLy8gSWRlbXBvdGVudDoga2VlcCBleGFjdGx5IG9uZSBjb250cm9sLiBBIFJlYWN0IHJlLXJlbmRlciBvZiB0aGUgYnViYmxlIGNhbgogICAgICAvLyBsZWF2ZSBhIHN0YWxlIGNvbnRyb2wgYmVoaW5kIG9yIHRyYW5zaWVudGx5IGRlZmVhdCBhbiAiYWxyZWFkeSBkZWNvcmF0ZWQiCiAgICAgIC8vIGd1YXJkLCB3aGljaCBpcyB3aGF0IHByb2R1Y2VkIGR1cGxpY2F0ZSByb3dzIG9mIGJ1dHRvbnM7IHBydW5lIGFueSBleHRyYXMKICAgICAgLy8gZXZlcnkgc3dlZXAgYW5kIG9ubHkgYWRkIG9uZSB3aGVuIG5vbmUgcmVtYWluLgogICAgICB2YXIgZXhpc3RpbmcgPSBidWJibGUucXVlcnlTZWxlY3RvckFsbCA/IGJ1YmJsZS5xdWVyeVNlbGVjdG9yQWxsKCIuIiArIENPTlRST0xfUFJFRklYKSA6IG51bGw7CiAgICAgIGlmICghaGFzQ29weWFibGVDb250ZW50KGNvbnRlbnROb2RlT2YoYnViYmxlLCByb2xlKSwgcm9sZSkpIHsKICAgICAgICBpZiAoZXhpc3RpbmcgJiYgZXhpc3RpbmcubGVuZ3RoKSB7CiAgICAgICAgICBmb3IgKHZhciBqID0gZXhpc3RpbmcubGVuZ3RoIC0gMTsgaiA+PSAwOyBqLS0pIHsKICAgICAgICAgICAgaWYgKGV4aXN0aW5nW2pdICYmIGV4aXN0aW5nW2pdLnBhcmVudE5vZGUpIGV4aXN0aW5nW2pdLnBhcmVudE5vZGUucmVtb3ZlQ2hpbGQoZXhpc3Rpbmdbal0pOwogICAgICAgICAgfQogICAgICAgIH0KICAgICAgICByZXR1cm47CiAgICAgIH0KICAgICAgaWYgKGV4aXN0aW5nICYmIGV4aXN0aW5nLmxlbmd0aCkgewogICAgICAgIGZvciAodmFyIGkgPSBleGlzdGluZy5sZW5ndGggLSAxOyBpID49IDE7IGktLSkgewogICAgICAgICAgaWYgKGV4aXN0aW5nW2ldICYmIGV4aXN0aW5nW2ldLnBhcmVudE5vZGUpIGV4aXN0aW5nW2ldLnBhcmVudE5vZGUucmVtb3ZlQ2hpbGQoZXhpc3RpbmdbaV0pOwogICAgICAgIH0KICAgICAgICByZXR1cm47CiAgICAgIH0KICAgICAgdmFyIGNvbnRyb2wgPSBidWlsZENvbnRyb2woZnVuY3Rpb24gKCkgewogICAgICAgIHJldHVybiBjb3B5VGV4dChidWJibGVNYXJrZG93bihidWJibGUsIHJvbGUpKTsKICAgICAgfSwgIkNvcHkgYXMgTWFya2Rvd24iKTsKICAgICAgYnViYmxlLmFwcGVuZENoaWxkKGNvbnRyb2wpOwogICAgfSBjYXRjaCAoXykge30KICB9CgogIGZ1bmN0aW9uIGNvcHlDb252ZXJzYXRpb24oKSB7CiAgICB2YXIgYnViYmxlcyA9IHFzYShVU0VSX0JVQkJMRSArICIsIiArIEFTU0lTVEFOVF9CVUJCTEUpOwogICAgcmV0dXJuIGNvcHlUZXh0KGNvbnZlcnNhdGlvblRvTWFya2Rvd24oYnViYmxlcywgZnVuY3Rpb24gKGIpIHsKICAgICAgcmV0dXJuIGNvbnRlbnROb2RlT2YoYiwgY2xhc3NpZnlCdWJibGUoYikpOwogICAgfSkpOwogIH0KCiAgLy8gQSBzaW5nbGUgZmxvYXRpbmcgIkNvcHkgY29udmVyc2F0aW9uIiBpY29uLCBwcmVzZW50IG9ubHkgd2hpbGUgYSBjb252ZXJzYXRpb24KICAvLyBpcyBvcGVuIChzbyBpdCBuZXZlciBjbHV0dGVycyB0aGUgaGlzdG9yeS1saXN0IHZpZXcpLiBQaW5uZWQgdG9wLXJpZ2h0IGJ5IENTUywKICAvLyBjbGVhciBvZiB0aGUgY2hhdCBpbnB1dCBhdCB0aGUgYm90dG9tOyB0aGUgbW9zdC1yZWNlbnQtcHJvbXB0IHN0aWNreSBoZWFkZXIKICAvLyBzaXRzIHRvIGl0cyBsZWZ0LgogIGZ1bmN0aW9uIGluc3RhbGxDb252ZXJzYXRpb25Db250cm9sKCkgewogICAgdHJ5IHsKICAgICAgdmFyIGV4aXN0aW5nID0gcXMoZG9jdW1lbnQsICIuIiArIENPTlRST0xfUFJFRklYICsgIi1jb252ZXJzYXRpb24iKTsKICAgICAgdmFyIGhhc01lc3NhZ2VzID0gcXNhKFVTRVJfQlVCQkxFICsgIiwiICsgQVNTSVNUQU5UX0JVQkJMRSkubGVuZ3RoID4gMDsKICAgICAgaWYgKCFoYXNNZXNzYWdlcykgewogICAgICAgIGlmIChleGlzdGluZyAmJiBleGlzdGluZy5wYXJlbnROb2RlKSBleGlzdGluZy5wYXJlbnROb2RlLnJlbW92ZUNoaWxkKGV4aXN0aW5nKTsKICAgICAgICByZXR1cm47CiAgICAgIH0KICAgICAgaWYgKGV4aXN0aW5nKSByZXR1cm47CiAgICAgIHZhciBiYXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKTsKICAgICAgYmFyLmNsYXNzTmFtZSA9IENPTlRST0xfUFJFRklYICsgIi1jb252ZXJzYXRpb24iOwogICAgICBiYXIuYXBwZW5kQ2hpbGQoYnVpbGRDb250cm9sKGNvcHlDb252ZXJzYXRpb24sICJDb3B5IGNvbnZlcnNhdGlvbiIpKTsKICAgICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChiYXIpOwogICAgfSBjYXRjaCAoXykge30KICB9CgogIGZ1bmN0aW9uIHN3ZWVwKCkgewogICAgdmFyIGIgPSBxc2EoVVNFUl9CVUJCTEUgKyAiLCIgKyBBU1NJU1RBTlRfQlVCQkxFKTsKICAgIGZvciAodmFyIGkgPSAwOyBpIDwgYi5sZW5ndGg7IGkrKykgZGVjb3JhdGUoYltpXSk7CiAgICBpbnN0YWxsQ29udmVyc2F0aW9uQ29udHJvbCgpOwogIH0KCiAgZnVuY3Rpb24gYm9vdCgpIHsKICAgIHRyeSB7CiAgICAgIHZhciB0YXJnZXQgPSAoTUVTU0FHRVNfQ09OVEFJTkVSICYmIHFzKGRvY3VtZW50LCBNRVNTQUdFU19DT05UQUlORVIpKSB8fCBkb2N1bWVudC5ib2R5OwogICAgICBzd2VlcCgpOwogICAgICBpZiAodHlwZW9mIE11dGF0aW9uT2JzZXJ2ZXIgPT09ICJ1bmRlZmluZWQiKSByZXR1cm47CiAgICAgIHZhciBvYnMgPSBuZXcgTXV0YXRpb25PYnNlcnZlcihmdW5jdGlvbiAoKSB7IHN3ZWVwKCk7IH0pOwogICAgICBvYnMub2JzZXJ2ZSh0YXJnZXQsIHsgY2hpbGRMaXN0OiB0cnVlLCBzdWJ0cmVlOiB0cnVlIH0pOwogICAgfSBjYXRjaCAoXykge30KICB9Cn0pKCk7Cg==").decode("utf-8") INJECT_CSS = base64.b64decode("LmNjLW1kLWNvcHkgewogIGRpc3BsYXk6IGlubGluZS1mbGV4OwogIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgdmVydGljYWwtYWxpZ246IG1pZGRsZTsKICBtYXJnaW4tbGVmdDogNnB4Owp9Ci5jYy1tZC1jb3B5LWJ0biB7CiAgZGlzcGxheTogaW5saW5lLWZsZXg7CiAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjsKICBwYWRkaW5nOiAycHg7CiAgY29sb3I6IHZhcigtLXZzY29kZS1mb3JlZ3JvdW5kKTsKICBiYWNrZ3JvdW5kOiB0cmFuc3BhcmVudDsKICBib3JkZXI6IG5vbmU7CiAgYm9yZGVyLXJhZGl1czogNHB4OwogIGN1cnNvcjogcG9pbnRlcjsKICBvcGFjaXR5OiAwLjY7Cn0KLmNjLW1kLWNvcHktYnRuIHN2ZyB7CiAgZGlzcGxheTogYmxvY2s7CiAgd2lkdGg6IDE0cHg7CiAgaGVpZ2h0OiAxNHB4Owp9Ci5jYy1tZC1jb3B5LWJ0bjpob3ZlciB7CiAgb3BhY2l0eTogMTsKICBiYWNrZ3JvdW5kOiB2YXIoLS12c2NvZGUtdG9vbGJhci1ob3ZlckJhY2tncm91bmQsIHJnYmEoMTI4LCAxMjgsIDEyOCwgMC4xNSkpOwp9Ci8qIFN1Y2Nlc3Mgc3RhdGU6IHRoZSBpY29uIGlzIGEgZ3JlZW4gY2hlY2ttYXJrIGZvciBhIG1vbWVudCBhZnRlciBhIHJlYWwgY29weS4gKi8KLmNjLW1kLWNvcHktYnRuLmNjLW1kLWNvcHktb2ssCi5jYy1tZC1jb3B5LWJ0bi5jYy1tZC1jb3B5LW9rOmhvdmVyIHsKICBvcGFjaXR5OiAxOwogIGNvbG9yOiB2YXIoLS12c2NvZGUtY2hhcnRzLWdyZWVuLCB2YXIoLS12c2NvZGUtdGVzdGluZy1pY29uUGFzc2VkLCAjODlkMTg1KSk7CiAgYmFja2dyb3VuZDogdHJhbnNwYXJlbnQ7Cn0KLyogV2hvbGUtY29udmVyc2F0aW9uIGNvcHk6IGEgc2luZ2xlIGZsb2F0aW5nIGljb24gcGlubmVkIHRvIHRoZSB0b3AtcmlnaHQgY29ybmVyLAogICBjbGVhciBvZiB0aGUgY2hhdCBpbnB1dCBhdCB0aGUgYm90dG9tLiBTaG93biBvbmx5IHdoaWxlIGEgY29udmVyc2F0aW9uIGlzIG9wZW4KICAgKHRoZSBJSUZFIGFkZHMvcmVtb3ZlcyBpdCkuIE51ZGdlIHRvcC9yaWdodCBoZXJlIGlmIGl0IGNyb3dkcyB0aGUgc3RpY2t5IGhlYWRlci4gKi8KLmNjLW1kLWNvcHktY29udmVyc2F0aW9uIHsKICBwb3NpdGlvbjogZml4ZWQ7CiAgdG9wOiAyNnB4OwogIHJpZ2h0OiA0cHg7CiAgei1pbmRleDogMzA7CiAgZGlzcGxheTogaW5saW5lLWZsZXg7CiAgcGFkZGluZzogMnB4OwogIGJhY2tncm91bmQ6IHZhcigtLXZzY29kZS1lZGl0b3JXaWRnZXQtYmFja2dyb3VuZCk7CiAgYm9yZGVyOiAxcHggc29saWQgdmFyKC0tdnNjb2RlLXdpZGdldC1ib3JkZXIsIHRyYW5zcGFyZW50KTsKICBib3JkZXItcmFkaXVzOiA2cHg7CiAgb3BhY2l0eTogMC44NTsKfQouY2MtbWQtY29weS1jb252ZXJzYXRpb24gLmNjLW1kLWNvcHkgewogIG1hcmdpbi1sZWZ0OiAwOwp9Ci5jYy1tZC1jb3B5LWNvbnZlcnNhdGlvbjpob3ZlciB7CiAgb3BhY2l0eTogMTsKfQo=").decode("utf-8") # << " + l; }).join("\n"); out += inner + "\n\n"; - } else if (tag === "HR") out += "---\n\n"; + } else if (tag === "DETAILS") out += block(c).trim() + "\n\n"; + else if (tag === "SUMMARY") out += inline(c).trim() + "\n\n"; + else if (tag === "HR") out += "---\n\n"; else if (tag === "TABLE") out += table(c) + "\n"; else if (tag === "BR") out += "\n"; else if (tag === "STRONG" || tag === "B" || tag === "EM" || tag === "I" || @@ -177,11 +179,12 @@ } // Class-prefix hooks for non-content chrome that renders *inside* an assistant - // bubble (verified on 2.1.170; Task 6 re-pins these). tool*/thinking_ are the v1 - // exclusions; unknownContent_ is the renderer's fallback for unrecognized block + // bubble (verified on 2.1.170; Task 6 re-pins these). Tool blocks are excluded + // from message copy; thinking summaries are visible content and must remain + // copyable. unknownContent_ is the renderer's fallback for unrecognized block // types, so stripping it makes a *future* block type fail safe to excluded rather // than leaking "Unsupported content" into the copy. Re-pin if a prefix moves. - var CHROME_PREFIXES = ["toolUse_", "toolResult_", "toolReference_", "thinking_", "unknownContent_"]; + var CHROME_PREFIXES = ["toolUse_", "toolResult_", "toolReference_", "unknownContent_"]; // True for any node that must never appear in copied output: our own controls, // the rating widget (`data-message-rating` + its "Thanks for your feedback" @@ -199,11 +202,11 @@ // message's text content only. This is a CORRECTNESS GATE, not cosmetic: the // default content node is the whole bubble (all content-block siblings, so multi- // block assistant turns are captured), and this strip-list is the only thing - // keeping the rating widget and v1-excluded blocks out of the copy. + // keeping the rating widget and excluded tool/fallback blocks out of the copy. function sanitizeClone(contentNode) { var clone = contentNode.cloneNode(true); (function strip(node) { - var kids = (node.childNodes || []).slice(); + var kids = Array.prototype.slice.call(node.childNodes || []); for (var i = 0; i < kids.length; i++) { var c = kids[i]; if (c.nodeType === 1 && isChrome(c)) { node.removeChild(c); continue; } @@ -213,6 +216,19 @@ return clone; } + function hasCopyableContent(contentNode, role) { + function walk(node) { + if (!node) return false; + if (node.nodeType === 3) return !!(node.textContent || "").trim(); + if (node.nodeType !== 1) return false; + if (isChrome(node)) return false; + var kids = node.childNodes || []; + for (var i = 0; i < kids.length; i++) if (walk(kids[i])) return true; + return false; + } + return walk(contentNode); + } + function classifyBubble(node) { if (node.nodeType !== 1) return null; if (hasPrefix(node, "userMessageContainer_")) return "user"; @@ -244,7 +260,7 @@ } else if (typeof module !== "undefined" && module.exports) { module.exports = { htmlToMarkdown: htmlToMarkdown, sanitizeClone: sanitizeClone, classifyBubble: classifyBubble, conversationToMarkdown: conversationToMarkdown, - copyText: copyText }; + hasCopyableContent: hasCopyableContent, copyText: copyText }; } // ---- live-webview wiring (runs only when a document exists) ---------------- @@ -254,7 +270,7 @@ // The content node to convert/copy: the optional ASSISTANT_CONTENT wrapper if // pinned and present, else the bubble itself. The bubble already contains every // content-block sibling of a multi-block turn, and sanitizeClone strips the - // chrome (rating widget, tool/thinking/unknown blocks, buttons, our controls) + // chrome (rating widget, tool/unknown blocks, buttons, our controls) // either way -- so this is a narrowing, never the thing that guarantees // correctness. function contentNodeOf(bubble, role) { @@ -378,6 +394,14 @@ // guard, which is what produced duplicate rows of buttons; prune any extras // every sweep and only add one when none remain. var existing = bubble.querySelectorAll ? bubble.querySelectorAll("." + CONTROL_PREFIX) : null; + if (!hasCopyableContent(contentNodeOf(bubble, role), role)) { + if (existing && existing.length) { + for (var j = existing.length - 1; j >= 0; j--) { + if (existing[j] && existing[j].parentNode) existing[j].parentNode.removeChild(existing[j]); + } + } + return; + } if (existing && existing.length) { for (var i = existing.length - 1; i >= 1; i--) { if (existing[i] && existing[i].parentNode) existing[i].parentNode.removeChild(existing[i]); diff --git a/launcher/claudemax b/launcher/claudemax index d82c319..50698da 100755 --- a/launcher/claudemax +++ b/launcher/claudemax @@ -46,6 +46,7 @@ # export CC_PATCH_MD_COPY=0 # no copy controls / webview append (1) # export CC_WORKAROUNDS=0 # master: disable every fix (1) # export CC_RECONCILE=0 # do not touch the webview bundle (1) +# export CC_SCRUB_ROUTING=1 # force the default Anthropic account (0) # # The real `claude` must be installed. This wrapper finds it automatically; if it # cannot, set CLAUDE_REAL_BIN to the full path of your real claude binary. @@ -144,6 +145,35 @@ CC_PATCH_MD_COPY="${CC_PATCH_MD_COPY:-1}" # Longer network timeout for large requests: # export API_TIMEOUT_MS="${API_TIMEOUT_MS:-600000}" +# --- Routing scrub + local environment -------------------------------------- +# +# CC_SCRUB_ROUTING=1 clears third-party model-routing variables before launch so +# Claude Code always uses the default Anthropic account. Useful when you also run +# wrappers (e.g. a DeepSeek launcher) that export ANTHROPIC_BASE_URL / +# ANTHROPIC_AUTH_TOKEN / *_MODEL to point Claude Code at a non-Anthropic model. +# Default 0: leave the environment as-is. +CC_SCRUB_ROUTING="${CC_SCRUB_ROUTING:-0}" +if [ "$CC_SCRUB_ROUTING" != "0" ]; then + unset CLAUDE_CONFIG_DIR \ + ANTHROPIC_BASE_URL ANTHROPIC_AUTH_TOKEN ANTHROPIC_MODEL \ + ANTHROPIC_DEFAULT_OPUS_MODEL ANTHROPIC_DEFAULT_SONNET_MODEL \ + ANTHROPIC_DEFAULT_HAIKU_MODEL CLAUDE_CODE_SUBAGENT_MODEL \ + ANTHROPIC_DEFAULT_OPUS_MODEL_NAME ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION \ + ANTHROPIC_DEFAULT_SONNET_MODEL_NAME ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION \ + ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION \ + ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES \ + ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES \ + ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES 2>/dev/null || true +fi + +# Personal/local exports go between the markers below. They are a stable splice +# point: the Linux deploy step and the Windows build.ps1 inject a private env +# file here, so a personal build never hand-merges into the launcher body. +# Anything set here (effort level, API timeout, even routing) applies this launch +# and, coming after the scrub above, wins over it. +# >>> ccwa-local-env >>> +# <<< ccwa-local-env <<< + # --- Inject the thinking-display fix into the launch args ------------------- # # Fire on a real agent invocation. Surfaces signal a real run differently: @@ -238,13 +268,15 @@ fi # untouched. # # context-icon feature - component `FJe` in webview/index.js: -# if(c>=50)return null -> if(c>=101)return null}/*ccwa-context-icon*/ -# `c` is "% of context remaining" (maxes at 100), so >=101 never fires and the -# icon renders whenever a context window is known; the t===0 "no session yet" -# guard is left intact. The trailing /*ccwa-context-icon*/ is our ownership -# marker. Maintenance: this keys off the stable string ">=50)return null}", not -# the minified component name; if a future build changes that substring, apply -# no-ops loudly (a one-line warning) until the anchor here is updated. +# if(t===0)return null;if(c>=50)return null} +# -> if(c>=101)return null}/*ccwa-context-icon:t:c*/ +# `c` is "% of context remaining" (maxes at 100), so >=101 never fires. Removing +# the t===0 guard keeps the icon visible across a reload gap; it may briefly show +# 0% until the webview receives fresh usage data. The trailing +# /*ccwa-context-icon::*/ is our ownership marker. +# Maintenance: this keys off the minified guard pair shape above, not the +# component name or exact minified variable names; if a future build changes that +# shape, apply no-ops loudly (a one-line warning) until the anchor here is updated. # A bundle feature is enabled when the master switch is on AND its own toggle is # on. CC_WORKAROUNDS=0 forces every feature off, so reconcile reverts to clean. @@ -261,33 +293,34 @@ _cc_feature_enabled() { # already present/absent, so chaining them is safe and idempotent. _cc_apply_context_icon() { local f="$1" tmp count - if grep -q '/\*ccwa-context-icon\*/' "$f" 2>/dev/null; then return 0; fi # already marked - count="$( (grep -o '>=50)return null}' "$f" 2>/dev/null || true) | wc -l | tr -d ' ')" + if grep -q '/\*ccwa-context-icon' "$f" 2>/dev/null; then return 0; fi # already marked + count="$( (grep -E -o 'if\([A-Za-z_$][A-Za-z0-9_$]*===0\)return null;if\([A-Za-z_$][A-Za-z0-9_$]*>=50\)return null\}' "$f" 2>/dev/null || true) | wc -l | tr -d ' ')" if [ "$count" = "0" ]; then echo "claudemax: context-icon anchor not found in $f (extension changed?); skipping" >&2 return 0 fi if [ "$count" != "1" ]; then return 0; fi # ambiguous (version changed) - skip tmp="${f}.ccapply.$$" - if sed 's#>=50)return null}#>=101)return null}/*ccwa-context-icon*/#' "$f" > "$tmp" 2>/dev/null \ - && [ -s "$tmp" ] && grep -q '/\*ccwa-context-icon\*/' "$tmp" 2>/dev/null; then + if sed 's#if(\([A-Za-z_$][A-Za-z0-9_$]*\)===0)return null;if(\([A-Za-z_$][A-Za-z0-9_$]*\)>=50)return null}#if(\2>=101)return null}/*ccwa-context-icon:\1:\2*/#' "$f" > "$tmp" 2>/dev/null \ + && [ -s "$tmp" ] && grep -q '/\*ccwa-context-icon' "$tmp" 2>/dev/null; then cat "$tmp" > "$f" 2>/dev/null || true fi rm -f "$tmp" 2>/dev/null || true } _cc_undo_context_icon() { - # Revert our edit to the pristine upstream form. Two ownership fingerprints are - # recognized: the current MARKED form, and the legacy BARE form that older - # launcher/standalone versions wrote before the marker existed. `>=101)return - # null}` is dead upstream code (c maxes at 100), so it appears only as our own - # output; adopting it lets a legacy install revert and upgrade cleanly instead - # of warning on every launch. The MARKED substitution runs first because the - # bare string is a prefix of the marked one. + # Revert our edit to the pristine upstream form. Recognized fingerprints are: + # the current metadata-marked form, and legacy marked/bare forms that older + # versions wrote with fixed t/c names. The marked substitutions run first + # because bare strings are prefixes of marked strings. local f="$1" tmp grep -qF '>=101)return null}' "$f" 2>/dev/null || return 0 # nothing of ours tmp="${f}.ccundo.$$" - if sed -e 's#>=101)return null}/\*ccwa-context-icon\*/#>=50)return null}#g' \ + if sed -e 's#if(\([A-Za-z_$][A-Za-z0-9_$]*\)>=101)return null}/\*ccwa-context-icon:\([A-Za-z_$][A-Za-z0-9_$]*\):\1\*/#if(\2===0)return null;if(\1>=50)return null}#g' \ + -e 's#if(t===0)return null;if(c>=101)return null}/\*ccwa-context-icon\*/#if(t===0)return null;if(c>=50)return null}#g' \ + -e 's#if(c>=101)return null}/\*ccwa-context-icon\*/#if(t===0)return null;if(c>=50)return null}#g' \ + -e 's#if(t===0)return null;if(c>=101)return null}#if(t===0)return null;if(c>=50)return null}#g' \ + -e 's#if(c>=101)return null}#if(t===0)return null;if(c>=50)return null}#g' \ -e 's#>=101)return null}#>=50)return null}#g' "$f" > "$tmp" 2>/dev/null \ && [ -s "$tmp" ]; then cat "$tmp" > "$f" 2>/dev/null || true @@ -458,7 +491,9 @@ _cc_md_copy_js() { cat <<'CCMDCOPYJS' } else if (tag === "BLOCKQUOTE") { var inner = block(c).trim().split("\n").map(function (l) { return "> " + l; }).join("\n"); out += inner + "\n\n"; - } else if (tag === "HR") out += "---\n\n"; + } else if (tag === "DETAILS") out += block(c).trim() + "\n\n"; + else if (tag === "SUMMARY") out += inline(c).trim() + "\n\n"; + else if (tag === "HR") out += "---\n\n"; else if (tag === "TABLE") out += table(c) + "\n"; else if (tag === "BR") out += "\n"; else if (tag === "STRONG" || tag === "B" || tag === "EM" || tag === "I" || @@ -484,11 +519,12 @@ _cc_md_copy_js() { cat <<'CCMDCOPYJS' } // Class-prefix hooks for non-content chrome that renders *inside* an assistant - // bubble (verified on 2.1.170; Task 6 re-pins these). tool*/thinking_ are the v1 - // exclusions; unknownContent_ is the renderer's fallback for unrecognized block + // bubble (verified on 2.1.170; Task 6 re-pins these). Tool blocks are excluded + // from message copy; thinking summaries are visible content and must remain + // copyable. unknownContent_ is the renderer's fallback for unrecognized block // types, so stripping it makes a *future* block type fail safe to excluded rather // than leaking "Unsupported content" into the copy. Re-pin if a prefix moves. - var CHROME_PREFIXES = ["toolUse_", "toolResult_", "toolReference_", "thinking_", "unknownContent_"]; + var CHROME_PREFIXES = ["toolUse_", "toolResult_", "toolReference_", "unknownContent_"]; // True for any node that must never appear in copied output: our own controls, // the rating widget (`data-message-rating` + its "Thanks for your feedback" @@ -506,11 +542,11 @@ _cc_md_copy_js() { cat <<'CCMDCOPYJS' // message's text content only. This is a CORRECTNESS GATE, not cosmetic: the // default content node is the whole bubble (all content-block siblings, so multi- // block assistant turns are captured), and this strip-list is the only thing - // keeping the rating widget and v1-excluded blocks out of the copy. + // keeping the rating widget and excluded tool/fallback blocks out of the copy. function sanitizeClone(contentNode) { var clone = contentNode.cloneNode(true); (function strip(node) { - var kids = (node.childNodes || []).slice(); + var kids = Array.prototype.slice.call(node.childNodes || []); for (var i = 0; i < kids.length; i++) { var c = kids[i]; if (c.nodeType === 1 && isChrome(c)) { node.removeChild(c); continue; } @@ -520,6 +556,19 @@ _cc_md_copy_js() { cat <<'CCMDCOPYJS' return clone; } + function hasCopyableContent(contentNode, role) { + function walk(node) { + if (!node) return false; + if (node.nodeType === 3) return !!(node.textContent || "").trim(); + if (node.nodeType !== 1) return false; + if (isChrome(node)) return false; + var kids = node.childNodes || []; + for (var i = 0; i < kids.length; i++) if (walk(kids[i])) return true; + return false; + } + return walk(contentNode); + } + function classifyBubble(node) { if (node.nodeType !== 1) return null; if (hasPrefix(node, "userMessageContainer_")) return "user"; @@ -551,7 +600,7 @@ _cc_md_copy_js() { cat <<'CCMDCOPYJS' } else if (typeof module !== "undefined" && module.exports) { module.exports = { htmlToMarkdown: htmlToMarkdown, sanitizeClone: sanitizeClone, classifyBubble: classifyBubble, conversationToMarkdown: conversationToMarkdown, - copyText: copyText }; + hasCopyableContent: hasCopyableContent, copyText: copyText }; } // ---- live-webview wiring (runs only when a document exists) ---------------- @@ -561,7 +610,7 @@ _cc_md_copy_js() { cat <<'CCMDCOPYJS' // The content node to convert/copy: the optional ASSISTANT_CONTENT wrapper if // pinned and present, else the bubble itself. The bubble already contains every // content-block sibling of a multi-block turn, and sanitizeClone strips the - // chrome (rating widget, tool/thinking/unknown blocks, buttons, our controls) + // chrome (rating widget, tool/unknown blocks, buttons, our controls) // either way -- so this is a narrowing, never the thing that guarantees // correctness. function contentNodeOf(bubble, role) { @@ -685,6 +734,14 @@ _cc_md_copy_js() { cat <<'CCMDCOPYJS' // guard, which is what produced duplicate rows of buttons; prune any extras // every sweep and only add one when none remain. var existing = bubble.querySelectorAll ? bubble.querySelectorAll("." + CONTROL_PREFIX) : null; + if (!hasCopyableContent(contentNodeOf(bubble, role), role)) { + if (existing && existing.length) { + for (var j = existing.length - 1; j >= 0; j--) { + if (existing[j] && existing[j].parentNode) existing[j].parentNode.removeChild(existing[j]); + } + } + return; + } if (existing && existing.length) { for (var i = existing.length - 1; i >= 1; i--) { if (existing[i] && existing[i].parentNode) existing[i].parentNode.removeChild(existing[i]); diff --git a/launcher/claudemax.win.js b/launcher/claudemax.win.js index 5900fa4..6789e41 100644 --- a/launcher/claudemax.win.js +++ b/launcher/claudemax.win.js @@ -47,6 +47,7 @@ // set CC_PATCH_MD_COPY=0 no copy controls / webview append (default: 1) // set CC_WORKAROUNDS=0 master: disable every fix (default: 1) // set CC_RECONCILE=0 do not touch the webview bundle (default: 1) +// set CC_SCRUB_ROUTING=1 force the default Anthropic account (default: 0) // // The real `claude` must be installed. This wrapper finds it automatically // (native install `claude.exe` or npm `claude.cmd`); if it cannot, set the @@ -233,21 +234,45 @@ if (!claude) { // --- Restore the always-visible context-usage icon (patches the webview) ---- // // Idempotent edit to component `FJe` in the extension's webview/index.js: -// if(c>=50)return null -> if(c>=101)return null -// `c` is "% of context remaining"; it maxes at 100, so >=101 never fires and the -// icon renders whenever a context window is known (the t===0 "no session yet" -// guard is left intact). Best-effort: every step is wrapped so it can never block -// the launch; a one-time backup is made and the write goes through a temp + rename -// so a failed write leaves the original untouched. Re-applied each launch, so an -// extension update that reinstalls a fresh bundle is re-patched next launch. +// if(t===0)return null;if(c>=50)return null} +// -> if(c>=101)return null}/*ccwa-context-icon:t:c*/ +// `c` is "% of context remaining"; it maxes at 100, so >=101 never fires. Removing +// the t===0 guard keeps the icon visible across a reload gap; it may briefly show +// 0% until the webview receives fresh usage data. Best-effort: every step is +// wrapped so it can never block the launch; a one-time backup is made and the +// write goes through a temp + rename so a failed write leaves the original +// untouched. Re-applied each launch, so an extension update that reinstalls a +// fresh bundle is re-patched next launch. // -// Maintenance note: this keys off the stable string ">=50)return null}", not the -// minified component name. If a future build changes that exact substring, the -// routine safely no-ops until the anchor here is updated. -const ICON_OLD = ">=50)return null}"; -const ICON_MARKER = "/*ccwa-context-icon*/"; -const ICON_BARE = ">=101)return null}"; // legacy unmarked form (older launcher/standalone) -const ICON_NEW = ICON_BARE + ICON_MARKER; +// Maintenance note: this keys off the minified guard pair shape below, not the +// component name or exact minified variable names. If a future build changes that +// shape, the routine safely no-ops until the anchor here is updated. +const ICON_IDENT = "[A-Za-z_$][A-Za-z0-9_$]*"; +const ICON_OLD_RE = new RegExp( + "if\\((" + ICON_IDENT + ")===0\\)return null;if\\((" + ICON_IDENT + ")>=50\\)return null\\}", + "g" +); +const ICON_MARKED_RE = new RegExp( + "if\\((" + ICON_IDENT + ")>=101\\)return null\\}/\\*ccwa-context-icon:(" + + ICON_IDENT + + "):\\1\\*/", + "g" +); +const ICON_OLD = "if(t===0)return null;if(c>=50)return null}"; +const ICON_LEGACY_MARKER = "/*ccwa-context-icon*/"; +const ICON_BARE = "if(c>=101)return null}"; +const ICON_NEW = "if(c>=101)return null}/*ccwa-context-icon:t:c*/"; +const ICON_LEGACY_NEW_CURRENT = ICON_BARE + ICON_LEGACY_MARKER; +const ICON_LEGACY_BARE = "if(t===0)return null;if(c>=101)return null}"; +const ICON_LEGACY_NEW = ICON_LEGACY_BARE + ICON_LEGACY_MARKER; + +function iconOld(firstVar, remainingVar) { + return `if(${firstVar}===0)return null;if(${remainingVar}>=50)return null}`; +} + +function iconNew(firstVar, remainingVar) { + return `if(${remainingVar}>=101)return null}/*ccwa-context-icon:${firstVar}:${remainingVar}*/`; +} // Bundle-patch feature registry. Each feature is idempotent (apply/undo are // no-ops when their target state already holds) and reversible; undo keys off @@ -255,26 +280,39 @@ const ICON_NEW = ICON_BARE + ICON_MARKER; // older version wrote), so it reverses ONLY our own edits. Order matters: apply // runs forward, undo runs in reverse. function applyContextIcon(data) { - if (data.indexOf(ICON_MARKER) !== -1) return data; // already applied - const n = data.split(ICON_OLD).length - 1; + if (ICON_MARKED_RE.test(data)) { + ICON_MARKED_RE.lastIndex = 0; + return data; // already applied + } + ICON_MARKED_RE.lastIndex = 0; + const base = undoContextIcon(data); + const matches = Array.from(base.matchAll(ICON_OLD_RE)); + const n = matches.length; if (n === 0) { console.error( "claudemax: context-icon anchor not found (extension changed?); skipping" ); - return data; + return base; } - if (n !== 1) return data; // ambiguous (version changed) - skip - return data.replace(ICON_OLD, ICON_NEW); + if (n !== 1) return base; // ambiguous (version changed) - skip + return base.replace(ICON_OLD_RE, (_, firstVar, remainingVar) => + iconNew(firstVar, remainingVar) + ); } function undoContextIcon(data) { - // Revert our edit to the pristine upstream form. Two ownership fingerprints - // are recognized: the current MARKED form, and the legacy BARE form older - // versions wrote before the marker existed. ICON_BARE is dead upstream code - // (c maxes at 100), so it appears only as our own output; adopting it lets a - // legacy install revert/upgrade cleanly. Marked must go first: ICON_BARE is a - // prefix of ICON_NEW. - return data.split(ICON_NEW).join(ICON_OLD).split(ICON_BARE).join(ICON_OLD); + // Revert our edit to the pristine upstream form. Recognized fingerprints are: + // the current metadata marker, and legacy marked/bare forms that older + // versions wrote with fixed t/c names. Marked forms must go first because bare + // strings are prefixes of marked strings. + return data + .replace(ICON_MARKED_RE, (_, remainingVar, firstVar) => + iconOld(firstVar, remainingVar) + ) + .split(ICON_LEGACY_NEW).join(ICON_OLD) + .split(ICON_LEGACY_NEW_CURRENT).join(ICON_OLD) + .split(ICON_LEGACY_BARE).join(ICON_OLD) + .split(ICON_BARE).join(ICON_OLD); } function contextIconEnabled() { @@ -292,7 +330,7 @@ function contextIconEnabled() { const MD_OPEN = "/* cc-md-copy v1 */"; const MD_CLOSE = "/* /cc-md-copy v1 */"; // >>>CCWA-MD-COPY-EMBED>>> (generated by tools/gen-embeds; do not edit) -const MD_COPY_JS = "/* cc-md-copy: per-message and whole-conversation copy (Markdown) for the\n * Claude Code VS Code webview. Self-contained IIFE appended to webview/index.js.\n * Each control is a single clipboard icon that flips to a checkmark for ~2s when a\n * copy actually succeeds (no text label, no menu). Additive and read-only w.r.t.\n * app state; keyed on stable CSS-module class prefixes, so it fails safe (controls\n * simply do not appear) if a prefix moves.\n * Exposes its pure functions for node unit tests; boot()s only in a real webview. */\n/* Leading ';' so that, appended after the bundle, this IIFE can never be parsed as\n * a call on the bundle's final expression if it lacks a trailing semicolon (ASI\n * safety across extension builds). */\n;(function () {\n \"use strict\";\n\n var CONTROL_PREFIX = \"cc-md-copy\"; // every injected node's class starts with this\n var USER_BUBBLE = '[class*=\"userMessageContainer_\"]';\n // Assistant message wrapper. Verified on 2.1.170: the render emits exactly one\n // `data-testid=\"assistant-message\"` div per assistant turn, with the rating\n // widget and content blocks as its children. (The earlier `[data-message-rating]`\n // was WRONG: that attribute sits on the nested rating control, which is also only\n // rendered behind an experiment+analytics gate.) Re-pinned in Task 6.\n var ASSISTANT_BUBBLE = '[data-testid=\"assistant-message\"]';\n var MESSAGES_CONTAINER = '[class*=\"messagesContainer_\"]'; // e.g. '[class*=\"timeline_\"]'; \"\" -> observe document.body\n // Optional narrowing only. MUST be a single wrapper around ALL content blocks,\n // not a per-block class (a turn has multiple blocks). \"\" -> use the bubble itself\n // (already aggregates all blocks; sanitizeClone is the correctness gate).\n var ASSISTANT_CONTENT = \"\";\n var FEEDBACK_MS = 2000; // how long the checkmark shows after a successful copy\n\n // ---- HTML -> Markdown (DOM walk) -------------------------------------------\n // Uses only: nodeType, tagName, childNodes, textContent, getAttribute, className.\n function htmlToMarkdown(root) {\n // Longest run of consecutive backticks in s, so a code delimiter/fence can be\n // chosen longer than anything inside it (else ``` in the content closes early).\n function backtickRun(s) {\n var max = 0, cur = 0;\n for (var i = 0; i < s.length; i++) {\n if (s.charAt(i) === \"`\") { cur++; if (cur > max) max = cur; } else cur = 0;\n }\n return max;\n }\n function fence(s, min) { var n = backtickRun(s) + 1; if (n < min) n = min; return new Array(n + 1).join(\"`\"); }\n function inline(node) {\n var out = \"\";\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 3) { out += c.textContent || \"\"; continue; }\n if (c.nodeType !== 1) continue;\n var tag = (c.tagName || \"\").toUpperCase();\n if (tag === \"BR\") out += \"\\n\";\n else if (tag === \"STRONG\" || tag === \"B\") out += \"**\" + inline(c) + \"**\";\n else if (tag === \"EM\" || tag === \"I\") out += \"*\" + inline(c) + \"*\";\n else if (tag === \"DEL\" || tag === \"S\") out += \"~~\" + inline(c) + \"~~\";\n else if (tag === \"CODE\") {\n var ct = c.textContent || \"\";\n var d = fence(ct, 1);\n // CommonMark strips one leading+trailing space, so pad when an edge is a\n // backtick to keep it from merging with the delimiter.\n var p = (ct.charAt(0) === \"`\" || ct.charAt(ct.length - 1) === \"`\") ? \" \" : \"\";\n out += d + p + ct + p + d;\n }\n else if (tag === \"A\") {\n var href = c.getAttribute ? c.getAttribute(\"href\") : null;\n var t = inline(c);\n out += href ? \"[\" + t + \"](\" + href + \")\" : t;\n } else out += inline(c); // unknown inline wrapper: keep text, drop tag\n }\n return out;\n }\n function langOf(codeEl) {\n var cls = \"\";\n if (codeEl) cls = (codeEl.getAttribute && codeEl.getAttribute(\"class\")) || codeEl.className || \"\";\n var m = /language-([A-Za-z0-9+#.\\-]+)/.exec(cls || \"\");\n return m ? m[1] : \"\";\n }\n function findChildTag(node, tag) {\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n if (kids[i].nodeType === 1 && (kids[i].tagName || \"\").toUpperCase() === tag) return kids[i];\n }\n return null;\n }\n function list(node, ordered, depth) {\n var out = \"\", n = 1;\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var li = kids[i];\n if (li.nodeType !== 1 || (li.tagName || \"\").toUpperCase() !== \"LI\") continue;\n var marker = ordered ? n++ + \". \" : \"- \";\n var indent = new Array(depth + 1).join(\" \");\n var lead = \"\", nested = \"\";\n var lk = li.childNodes || [];\n for (var j = 0; j < lk.length; j++) {\n var ch = lk[j];\n var ct = ch.nodeType === 1 ? (ch.tagName || \"\").toUpperCase() : \"\";\n if (ct === \"UL\") nested += list(ch, false, depth + 1);\n else if (ct === \"OL\") nested += list(ch, true, depth + 1);\n else if (ch.nodeType === 3) lead += ch.textContent || \"\";\n else lead += inline(ch);\n }\n out += indent + marker + lead.trim() + \"\\n\" + nested;\n }\n return out;\n }\n function table(node) {\n var rows = [];\n (function collect(container) {\n var kids = container.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType !== 1) continue;\n var t = (c.tagName || \"\").toUpperCase();\n if (t === \"THEAD\" || t === \"TBODY\" || t === \"TFOOT\") collect(c);\n else if (t === \"TR\") {\n var cells = [], cc = c.childNodes || [];\n for (var j = 0; j < cc.length; j++) {\n var d = cc[j];\n if (d.nodeType !== 1) continue;\n var dt = (d.tagName || \"\").toUpperCase();\n if (dt === \"TH\" || dt === \"TD\") cells.push(inline(d).trim());\n }\n rows.push(cells);\n }\n }\n })(node);\n if (!rows.length) return \"\";\n var head = rows[0], body = rows.slice(1);\n var sep = head.map(function () { return \"---\"; });\n var out = \"| \" + head.join(\" | \") + \" |\\n| \" + sep.join(\" | \") + \" |\\n\";\n for (var k = 0; k < body.length; k++) out += \"| \" + body[k].join(\" | \") + \" |\\n\";\n return out;\n }\n function block(node) {\n var out = \"\";\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 3) { if ((c.textContent || \"\").trim()) out += c.textContent; continue; }\n if (c.nodeType !== 1) continue;\n var tag = (c.tagName || \"\").toUpperCase();\n if (/^H[1-6]$/.test(tag)) out += new Array(+tag[1] + 1).join(\"#\") + \" \" + inline(c).trim() + \"\\n\\n\";\n else if (tag === \"P\") out += inline(c).trim() + \"\\n\\n\";\n else if (tag === \"UL\") out += list(c, false, 0) + \"\\n\";\n else if (tag === \"OL\") out += list(c, true, 0) + \"\\n\";\n else if (tag === \"PRE\") {\n var code = findChildTag(c, \"CODE\");\n var lang = langOf(code || c);\n var body = (code || c).textContent || \"\";\n var f = fence(body, 3);\n out += f + lang + \"\\n\" + body.replace(/\\n$/, \"\") + \"\\n\" + f + \"\\n\\n\";\n } else if (tag === \"BLOCKQUOTE\") {\n var inner = block(c).trim().split(\"\\n\").map(function (l) { return \"> \" + l; }).join(\"\\n\");\n out += inner + \"\\n\\n\";\n } else if (tag === \"HR\") out += \"---\\n\\n\";\n else if (tag === \"TABLE\") out += table(c) + \"\\n\";\n else if (tag === \"BR\") out += \"\\n\";\n else if (tag === \"STRONG\" || tag === \"B\" || tag === \"EM\" || tag === \"I\" ||\n tag === \"A\" || tag === \"CODE\" || tag === \"DEL\" || tag === \"S\")\n out += inline(c) + \"\\n\\n\";\n else out += block(c); // unknown wrapper: recurse (drop tag, keep content)\n }\n return out;\n }\n // block() dispatches on each CHILD's tag, treating the passed node as a plain\n // container. Wrap root in a one-off container so root's OWN tag is dispatched\n // too: callers pass either the bubble container (its block children render) or\n // a single block element like
    /
      /
    (now handled, not flattened).\n return block({ childNodes: [root] }).replace(/\\n{3,}/g, \"\\n\\n\").trim();\n }\n\n // ---- pure helpers ----------------------------------------------------------\n function hasPrefix(node, prefix) {\n if (node.nodeType !== 1 || typeof node.className !== \"string\") return false;\n var parts = node.className.split(/\\s+/);\n for (var i = 0; i < parts.length; i++) if (parts[i].indexOf(prefix) === 0) return true;\n return false;\n }\n\n // Class-prefix hooks for non-content chrome that renders *inside* an assistant\n // bubble (verified on 2.1.170; Task 6 re-pins these). tool*/thinking_ are the v1\n // exclusions; unknownContent_ is the renderer's fallback for unrecognized block\n // types, so stripping it makes a *future* block type fail safe to excluded rather\n // than leaking \"Unsupported content\" into the copy. Re-pin if a prefix moves.\n var CHROME_PREFIXES = [\"toolUse_\", \"toolResult_\", \"toolReference_\", \"thinking_\", \"unknownContent_\"];\n\n // True for any node that must never appear in copied output: our own controls,\n // the rating widget (`data-message-rating` + its \"Thanks for your feedback\"\n // text), any button (copy-code chrome), and the excluded content blocks above.\n function isChrome(node) {\n if (node.nodeType !== 1) return false;\n if ((node.tagName || \"\").toUpperCase() === \"BUTTON\") return true;\n if (node.getAttribute && node.getAttribute(\"data-message-rating\") !== null) return true;\n if (hasPrefix(node, CONTROL_PREFIX)) return true;\n for (var i = 0; i < CHROME_PREFIXES.length; i++) if (hasPrefix(node, CHROME_PREFIXES[i])) return true;\n return false;\n }\n\n // Deep-clone `contentNode`, then strip every chrome node so copied output is the\n // message's text content only. This is a CORRECTNESS GATE, not cosmetic: the\n // default content node is the whole bubble (all content-block siblings, so multi-\n // block assistant turns are captured), and this strip-list is the only thing\n // keeping the rating widget and v1-excluded blocks out of the copy.\n function sanitizeClone(contentNode) {\n var clone = contentNode.cloneNode(true);\n (function strip(node) {\n var kids = (node.childNodes || []).slice();\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 1 && isChrome(c)) { node.removeChild(c); continue; }\n if (c.nodeType === 1) strip(c);\n }\n })(clone);\n return clone;\n }\n\n function classifyBubble(node) {\n if (node.nodeType !== 1) return null;\n if (hasPrefix(node, \"userMessageContainer_\")) return \"user\";\n if (node.getAttribute && node.getAttribute(\"data-testid\") === \"assistant-message\") return \"assistant\";\n return null;\n }\n\n // Build the whole-conversation markdown from an ordered list of bubbles.\n // `contentOf(bubble)` resolves the content node (default: the bubble itself, so\n // every content block is included; sanitizeClone drops chrome); a default is\n // provided for tests.\n function conversationToMarkdown(bubbles, contentOf) {\n contentOf = contentOf || function (b) { return b; };\n var parts = [];\n for (var i = 0; i < bubbles.length; i++) {\n var role = classifyBubble(bubbles[i]);\n if (!role) continue;\n var clean = sanitizeClone(contentOf(bubbles[i]));\n var body = role === \"assistant\" ? htmlToMarkdown(clean) : (clean.textContent || \"\").trim();\n if (!body) continue;\n parts.push((role === \"user\" ? \"## User\" : \"## Assistant\") + \"\\n\\n\" + body);\n }\n return parts.join(\"\\n\\n\") + (parts.length ? \"\\n\" : \"\");\n }\n\n // ---- exports (node tests) / boot (real webview) ----------------------------\n if (typeof document !== \"undefined\") {\n boot();\n } else if (typeof module !== \"undefined\" && module.exports) {\n module.exports = { htmlToMarkdown: htmlToMarkdown, sanitizeClone: sanitizeClone,\n classifyBubble: classifyBubble, conversationToMarkdown: conversationToMarkdown,\n copyText: copyText };\n }\n\n // ---- live-webview wiring (runs only when a document exists) ----------------\n function qs(node, sel) { try { return sel && node.querySelector ? node.querySelector(sel) : null; } catch (_) { return null; } }\n function qsa(sel) { try { return Array.prototype.slice.call(document.querySelectorAll(sel)); } catch (_) { return []; } }\n\n // The content node to convert/copy: the optional ASSISTANT_CONTENT wrapper if\n // pinned and present, else the bubble itself. The bubble already contains every\n // content-block sibling of a multi-block turn, and sanitizeClone strips the\n // chrome (rating widget, tool/thinking/unknown blocks, buttons, our controls)\n // either way -- so this is a narrowing, never the thing that guarantees\n // correctness.\n function contentNodeOf(bubble, role) {\n if (role === \"assistant\" && ASSISTANT_CONTENT) {\n var n = qs(bubble, ASSISTANT_CONTENT);\n if (n) return n;\n }\n return bubble;\n }\n\n // Copy `s` via a synchronous execCommand(\"copy\") on an off-screen textarea, and\n // report whether it actually happened. Done first (and synchronously) because it\n // runs inside the click gesture and works whether or not the page is a secure\n // context -- so it covers remote / code-server, where the async Clipboard API is\n // simply absent. Restores the prior selection/focus so it is invisible.\n function execCopy(s) {\n try {\n if (typeof document === \"undefined\" || !document.createElement) return false;\n var prev = document.activeElement || null;\n var sel = document.getSelection ? document.getSelection() : null;\n var saved = (sel && sel.rangeCount) ? sel.getRangeAt(0) : null;\n var ta = document.createElement(\"textarea\");\n ta.value = s;\n ta.setAttribute(\"readonly\", \"\");\n ta.style.position = \"fixed\";\n ta.style.top = \"-1000px\";\n ta.style.left = \"0\";\n ta.style.opacity = \"0\";\n (document.body || document.documentElement).appendChild(ta);\n ta.focus();\n ta.select();\n var ok = false;\n try { ok = document.execCommand(\"copy\"); } catch (_) { ok = false; }\n if (ta.parentNode) ta.parentNode.removeChild(ta);\n if (saved && sel) { try { sel.removeAllRanges(); sel.addRange(saved); } catch (_) {} }\n if (prev && prev.focus) { try { prev.focus(); } catch (_) {} }\n return !!ok;\n } catch (_) { return false; }\n }\n\n // Copy `text` and resolve to whether the copy ACTUALLY happened, so callers only\n // show success on a real copy -- never a false \"copied\" (the original bug:\n // navigator.clipboard was undefined in the webview, the code fell through to\n // Promise.resolve(), and the UI claimed success while nothing was written). Empty\n // text is a non-copy -> false. execCommand first (gesture-safe, secure-context-\n // independent); the async Clipboard API is the fallback. Never throws.\n function copyText(text) {\n var s = (text == null) ? \"\" : String(text);\n if (!s) return Promise.resolve(false);\n if (execCopy(s)) return Promise.resolve(true);\n try {\n if (typeof navigator !== \"undefined\" && navigator.clipboard && navigator.clipboard.writeText) {\n return navigator.clipboard.writeText(s).then(\n function () { return true; },\n function () { return false; }\n );\n }\n } catch (_) {}\n return Promise.resolve(false);\n }\n\n function bubbleMarkdown(bubble, role) {\n var clean = sanitizeClone(contentNodeOf(bubble, role));\n return role === \"assistant\" ? htmlToMarkdown(clean) : (clean.textContent || \"\").trim();\n }\n\n // Inline SVG icons (currentColor, ~14px). Set via innerHTML on our own buttons\n // only; the markup never reaches copied content (sanitizeClone drops our nodes).\n var ICON_COPY = '';\n var ICON_CHECK = '';\n\n // Flip the button to a checkmark for FEEDBACK_MS, then restore. Idempotent across\n // rapid clicks (any pending restore is cleared first).\n function showCopied(btn) {\n try {\n if (btn.__ccTimer) clearTimeout(btn.__ccTimer);\n btn.classList.add(CONTROL_PREFIX + \"-ok\");\n btn.innerHTML = ICON_CHECK;\n btn.__ccTimer = setTimeout(function () {\n try { btn.classList.remove(CONTROL_PREFIX + \"-ok\"); btn.innerHTML = ICON_COPY; } catch (_) {}\n btn.__ccTimer = null;\n }, FEEDBACK_MS);\n } catch (_) {}\n }\n\n // Build a single control: one clipboard-icon button. `onCopy()` is invoked\n // synchronously on click (so the copy stays inside the user gesture) and must\n // return a Promise; the checkmark shows only when it resolves true. All\n // nodes carry the CONTROL_PREFIX class so sanitizeClone strips them from copies.\n function buildControl(onCopy, title) {\n var wrap = document.createElement(\"span\");\n wrap.className = CONTROL_PREFIX;\n var btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = CONTROL_PREFIX + \"-btn\";\n btn.title = title || \"Copy as Markdown\";\n btn.setAttribute(\"aria-label\", btn.title);\n btn.innerHTML = ICON_COPY;\n var busy = false;\n btn.addEventListener(\"click\", function (e) {\n e.stopPropagation();\n if (busy) return;\n busy = true;\n var p;\n try { p = onCopy(); } catch (_) { p = false; }\n Promise.resolve(p).then(\n function (ok) { busy = false; if (ok) showCopied(btn); },\n function () { busy = false; }\n );\n });\n wrap.appendChild(btn);\n return wrap;\n }\n\n function decorate(bubble) {\n try {\n var role = classifyBubble(bubble);\n if (!role) return;\n // Idempotent: keep exactly one control. A React re-render of the bubble can\n // leave a stale control behind or transiently defeat an \"already decorated\"\n // guard, which is what produced duplicate rows of buttons; prune any extras\n // every sweep and only add one when none remain.\n var existing = bubble.querySelectorAll ? bubble.querySelectorAll(\".\" + CONTROL_PREFIX) : null;\n if (existing && existing.length) {\n for (var i = existing.length - 1; i >= 1; i--) {\n if (existing[i] && existing[i].parentNode) existing[i].parentNode.removeChild(existing[i]);\n }\n return;\n }\n var control = buildControl(function () {\n return copyText(bubbleMarkdown(bubble, role));\n }, \"Copy as Markdown\");\n bubble.appendChild(control);\n } catch (_) {}\n }\n\n function copyConversation() {\n var bubbles = qsa(USER_BUBBLE + \",\" + ASSISTANT_BUBBLE);\n return copyText(conversationToMarkdown(bubbles, function (b) {\n return contentNodeOf(b, classifyBubble(b));\n }));\n }\n\n // A single floating \"Copy conversation\" icon, present only while a conversation\n // is open (so it never clutters the history-list view). Pinned top-right by CSS,\n // clear of the chat input at the bottom; the most-recent-prompt sticky header\n // sits to its left.\n function installConversationControl() {\n try {\n var existing = qs(document, \".\" + CONTROL_PREFIX + \"-conversation\");\n var hasMessages = qsa(USER_BUBBLE + \",\" + ASSISTANT_BUBBLE).length > 0;\n if (!hasMessages) {\n if (existing && existing.parentNode) existing.parentNode.removeChild(existing);\n return;\n }\n if (existing) return;\n var bar = document.createElement(\"div\");\n bar.className = CONTROL_PREFIX + \"-conversation\";\n bar.appendChild(buildControl(copyConversation, \"Copy conversation\"));\n document.body.appendChild(bar);\n } catch (_) {}\n }\n\n function sweep() {\n var b = qsa(USER_BUBBLE + \",\" + ASSISTANT_BUBBLE);\n for (var i = 0; i < b.length; i++) decorate(b[i]);\n installConversationControl();\n }\n\n function boot() {\n try {\n var target = (MESSAGES_CONTAINER && qs(document, MESSAGES_CONTAINER)) || document.body;\n sweep();\n if (typeof MutationObserver === \"undefined\") return;\n var obs = new MutationObserver(function () { sweep(); });\n obs.observe(target, { childList: true, subtree: true });\n } catch (_) {}\n }\n})();\n"; +const MD_COPY_JS = "/* cc-md-copy: per-message and whole-conversation copy (Markdown) for the\n * Claude Code VS Code webview. Self-contained IIFE appended to webview/index.js.\n * Each control is a single clipboard icon that flips to a checkmark for ~2s when a\n * copy actually succeeds (no text label, no menu). Additive and read-only w.r.t.\n * app state; keyed on stable CSS-module class prefixes, so it fails safe (controls\n * simply do not appear) if a prefix moves.\n * Exposes its pure functions for node unit tests; boot()s only in a real webview. */\n/* Leading ';' so that, appended after the bundle, this IIFE can never be parsed as\n * a call on the bundle's final expression if it lacks a trailing semicolon (ASI\n * safety across extension builds). */\n;(function () {\n \"use strict\";\n\n var CONTROL_PREFIX = \"cc-md-copy\"; // every injected node's class starts with this\n var USER_BUBBLE = '[class*=\"userMessageContainer_\"]';\n // Assistant message wrapper. Verified on 2.1.170: the render emits exactly one\n // `data-testid=\"assistant-message\"` div per assistant turn, with the rating\n // widget and content blocks as its children. (The earlier `[data-message-rating]`\n // was WRONG: that attribute sits on the nested rating control, which is also only\n // rendered behind an experiment+analytics gate.) Re-pinned in Task 6.\n var ASSISTANT_BUBBLE = '[data-testid=\"assistant-message\"]';\n var MESSAGES_CONTAINER = '[class*=\"messagesContainer_\"]'; // e.g. '[class*=\"timeline_\"]'; \"\" -> observe document.body\n // Optional narrowing only. MUST be a single wrapper around ALL content blocks,\n // not a per-block class (a turn has multiple blocks). \"\" -> use the bubble itself\n // (already aggregates all blocks; sanitizeClone is the correctness gate).\n var ASSISTANT_CONTENT = \"\";\n var FEEDBACK_MS = 2000; // how long the checkmark shows after a successful copy\n\n // ---- HTML -> Markdown (DOM walk) -------------------------------------------\n // Uses only: nodeType, tagName, childNodes, textContent, getAttribute, className.\n function htmlToMarkdown(root) {\n // Longest run of consecutive backticks in s, so a code delimiter/fence can be\n // chosen longer than anything inside it (else ``` in the content closes early).\n function backtickRun(s) {\n var max = 0, cur = 0;\n for (var i = 0; i < s.length; i++) {\n if (s.charAt(i) === \"`\") { cur++; if (cur > max) max = cur; } else cur = 0;\n }\n return max;\n }\n function fence(s, min) { var n = backtickRun(s) + 1; if (n < min) n = min; return new Array(n + 1).join(\"`\"); }\n function inline(node) {\n var out = \"\";\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 3) { out += c.textContent || \"\"; continue; }\n if (c.nodeType !== 1) continue;\n var tag = (c.tagName || \"\").toUpperCase();\n if (tag === \"BR\") out += \"\\n\";\n else if (tag === \"STRONG\" || tag === \"B\") out += \"**\" + inline(c) + \"**\";\n else if (tag === \"EM\" || tag === \"I\") out += \"*\" + inline(c) + \"*\";\n else if (tag === \"DEL\" || tag === \"S\") out += \"~~\" + inline(c) + \"~~\";\n else if (tag === \"CODE\") {\n var ct = c.textContent || \"\";\n var d = fence(ct, 1);\n // CommonMark strips one leading+trailing space, so pad when an edge is a\n // backtick to keep it from merging with the delimiter.\n var p = (ct.charAt(0) === \"`\" || ct.charAt(ct.length - 1) === \"`\") ? \" \" : \"\";\n out += d + p + ct + p + d;\n }\n else if (tag === \"A\") {\n var href = c.getAttribute ? c.getAttribute(\"href\") : null;\n var t = inline(c);\n out += href ? \"[\" + t + \"](\" + href + \")\" : t;\n } else out += inline(c); // unknown inline wrapper: keep text, drop tag\n }\n return out;\n }\n function langOf(codeEl) {\n var cls = \"\";\n if (codeEl) cls = (codeEl.getAttribute && codeEl.getAttribute(\"class\")) || codeEl.className || \"\";\n var m = /language-([A-Za-z0-9+#.\\-]+)/.exec(cls || \"\");\n return m ? m[1] : \"\";\n }\n function findChildTag(node, tag) {\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n if (kids[i].nodeType === 1 && (kids[i].tagName || \"\").toUpperCase() === tag) return kids[i];\n }\n return null;\n }\n function list(node, ordered, depth) {\n var out = \"\", n = 1;\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var li = kids[i];\n if (li.nodeType !== 1 || (li.tagName || \"\").toUpperCase() !== \"LI\") continue;\n var marker = ordered ? n++ + \". \" : \"- \";\n var indent = new Array(depth + 1).join(\" \");\n var lead = \"\", nested = \"\";\n var lk = li.childNodes || [];\n for (var j = 0; j < lk.length; j++) {\n var ch = lk[j];\n var ct = ch.nodeType === 1 ? (ch.tagName || \"\").toUpperCase() : \"\";\n if (ct === \"UL\") nested += list(ch, false, depth + 1);\n else if (ct === \"OL\") nested += list(ch, true, depth + 1);\n else if (ch.nodeType === 3) lead += ch.textContent || \"\";\n else lead += inline(ch);\n }\n out += indent + marker + lead.trim() + \"\\n\" + nested;\n }\n return out;\n }\n function table(node) {\n var rows = [];\n (function collect(container) {\n var kids = container.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType !== 1) continue;\n var t = (c.tagName || \"\").toUpperCase();\n if (t === \"THEAD\" || t === \"TBODY\" || t === \"TFOOT\") collect(c);\n else if (t === \"TR\") {\n var cells = [], cc = c.childNodes || [];\n for (var j = 0; j < cc.length; j++) {\n var d = cc[j];\n if (d.nodeType !== 1) continue;\n var dt = (d.tagName || \"\").toUpperCase();\n if (dt === \"TH\" || dt === \"TD\") cells.push(inline(d).trim());\n }\n rows.push(cells);\n }\n }\n })(node);\n if (!rows.length) return \"\";\n var head = rows[0], body = rows.slice(1);\n var sep = head.map(function () { return \"---\"; });\n var out = \"| \" + head.join(\" | \") + \" |\\n| \" + sep.join(\" | \") + \" |\\n\";\n for (var k = 0; k < body.length; k++) out += \"| \" + body[k].join(\" | \") + \" |\\n\";\n return out;\n }\n function block(node) {\n var out = \"\";\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 3) { if ((c.textContent || \"\").trim()) out += c.textContent; continue; }\n if (c.nodeType !== 1) continue;\n var tag = (c.tagName || \"\").toUpperCase();\n if (/^H[1-6]$/.test(tag)) out += new Array(+tag[1] + 1).join(\"#\") + \" \" + inline(c).trim() + \"\\n\\n\";\n else if (tag === \"P\") out += inline(c).trim() + \"\\n\\n\";\n else if (tag === \"UL\") out += list(c, false, 0) + \"\\n\";\n else if (tag === \"OL\") out += list(c, true, 0) + \"\\n\";\n else if (tag === \"PRE\") {\n var code = findChildTag(c, \"CODE\");\n var lang = langOf(code || c);\n var body = (code || c).textContent || \"\";\n var f = fence(body, 3);\n out += f + lang + \"\\n\" + body.replace(/\\n$/, \"\") + \"\\n\" + f + \"\\n\\n\";\n } else if (tag === \"BLOCKQUOTE\") {\n var inner = block(c).trim().split(\"\\n\").map(function (l) { return \"> \" + l; }).join(\"\\n\");\n out += inner + \"\\n\\n\";\n } else if (tag === \"DETAILS\") out += block(c).trim() + \"\\n\\n\";\n else if (tag === \"SUMMARY\") out += inline(c).trim() + \"\\n\\n\";\n else if (tag === \"HR\") out += \"---\\n\\n\";\n else if (tag === \"TABLE\") out += table(c) + \"\\n\";\n else if (tag === \"BR\") out += \"\\n\";\n else if (tag === \"STRONG\" || tag === \"B\" || tag === \"EM\" || tag === \"I\" ||\n tag === \"A\" || tag === \"CODE\" || tag === \"DEL\" || tag === \"S\")\n out += inline(c) + \"\\n\\n\";\n else out += block(c); // unknown wrapper: recurse (drop tag, keep content)\n }\n return out;\n }\n // block() dispatches on each CHILD's tag, treating the passed node as a plain\n // container. Wrap root in a one-off container so root's OWN tag is dispatched\n // too: callers pass either the bubble container (its block children render) or\n // a single block element like
    /
      /
    (now handled, not flattened).\n return block({ childNodes: [root] }).replace(/\\n{3,}/g, \"\\n\\n\").trim();\n }\n\n // ---- pure helpers ----------------------------------------------------------\n function hasPrefix(node, prefix) {\n if (node.nodeType !== 1 || typeof node.className !== \"string\") return false;\n var parts = node.className.split(/\\s+/);\n for (var i = 0; i < parts.length; i++) if (parts[i].indexOf(prefix) === 0) return true;\n return false;\n }\n\n // Class-prefix hooks for non-content chrome that renders *inside* an assistant\n // bubble (verified on 2.1.170; Task 6 re-pins these). Tool blocks are excluded\n // from message copy; thinking summaries are visible content and must remain\n // copyable. unknownContent_ is the renderer's fallback for unrecognized block\n // types, so stripping it makes a *future* block type fail safe to excluded rather\n // than leaking \"Unsupported content\" into the copy. Re-pin if a prefix moves.\n var CHROME_PREFIXES = [\"toolUse_\", \"toolResult_\", \"toolReference_\", \"unknownContent_\"];\n\n // True for any node that must never appear in copied output: our own controls,\n // the rating widget (`data-message-rating` + its \"Thanks for your feedback\"\n // text), any button (copy-code chrome), and the excluded content blocks above.\n function isChrome(node) {\n if (node.nodeType !== 1) return false;\n if ((node.tagName || \"\").toUpperCase() === \"BUTTON\") return true;\n if (node.getAttribute && node.getAttribute(\"data-message-rating\") !== null) return true;\n if (hasPrefix(node, CONTROL_PREFIX)) return true;\n for (var i = 0; i < CHROME_PREFIXES.length; i++) if (hasPrefix(node, CHROME_PREFIXES[i])) return true;\n return false;\n }\n\n // Deep-clone `contentNode`, then strip every chrome node so copied output is the\n // message's text content only. This is a CORRECTNESS GATE, not cosmetic: the\n // default content node is the whole bubble (all content-block siblings, so multi-\n // block assistant turns are captured), and this strip-list is the only thing\n // keeping the rating widget and excluded tool/fallback blocks out of the copy.\n function sanitizeClone(contentNode) {\n var clone = contentNode.cloneNode(true);\n (function strip(node) {\n var kids = Array.prototype.slice.call(node.childNodes || []);\n for (var i = 0; i < kids.length; i++) {\n var c = kids[i];\n if (c.nodeType === 1 && isChrome(c)) { node.removeChild(c); continue; }\n if (c.nodeType === 1) strip(c);\n }\n })(clone);\n return clone;\n }\n\n function hasCopyableContent(contentNode, role) {\n function walk(node) {\n if (!node) return false;\n if (node.nodeType === 3) return !!(node.textContent || \"\").trim();\n if (node.nodeType !== 1) return false;\n if (isChrome(node)) return false;\n var kids = node.childNodes || [];\n for (var i = 0; i < kids.length; i++) if (walk(kids[i])) return true;\n return false;\n }\n return walk(contentNode);\n }\n\n function classifyBubble(node) {\n if (node.nodeType !== 1) return null;\n if (hasPrefix(node, \"userMessageContainer_\")) return \"user\";\n if (node.getAttribute && node.getAttribute(\"data-testid\") === \"assistant-message\") return \"assistant\";\n return null;\n }\n\n // Build the whole-conversation markdown from an ordered list of bubbles.\n // `contentOf(bubble)` resolves the content node (default: the bubble itself, so\n // every content block is included; sanitizeClone drops chrome); a default is\n // provided for tests.\n function conversationToMarkdown(bubbles, contentOf) {\n contentOf = contentOf || function (b) { return b; };\n var parts = [];\n for (var i = 0; i < bubbles.length; i++) {\n var role = classifyBubble(bubbles[i]);\n if (!role) continue;\n var clean = sanitizeClone(contentOf(bubbles[i]));\n var body = role === \"assistant\" ? htmlToMarkdown(clean) : (clean.textContent || \"\").trim();\n if (!body) continue;\n parts.push((role === \"user\" ? \"## User\" : \"## Assistant\") + \"\\n\\n\" + body);\n }\n return parts.join(\"\\n\\n\") + (parts.length ? \"\\n\" : \"\");\n }\n\n // ---- exports (node tests) / boot (real webview) ----------------------------\n if (typeof document !== \"undefined\") {\n boot();\n } else if (typeof module !== \"undefined\" && module.exports) {\n module.exports = { htmlToMarkdown: htmlToMarkdown, sanitizeClone: sanitizeClone,\n classifyBubble: classifyBubble, conversationToMarkdown: conversationToMarkdown,\n hasCopyableContent: hasCopyableContent, copyText: copyText };\n }\n\n // ---- live-webview wiring (runs only when a document exists) ----------------\n function qs(node, sel) { try { return sel && node.querySelector ? node.querySelector(sel) : null; } catch (_) { return null; } }\n function qsa(sel) { try { return Array.prototype.slice.call(document.querySelectorAll(sel)); } catch (_) { return []; } }\n\n // The content node to convert/copy: the optional ASSISTANT_CONTENT wrapper if\n // pinned and present, else the bubble itself. The bubble already contains every\n // content-block sibling of a multi-block turn, and sanitizeClone strips the\n // chrome (rating widget, tool/unknown blocks, buttons, our controls)\n // either way -- so this is a narrowing, never the thing that guarantees\n // correctness.\n function contentNodeOf(bubble, role) {\n if (role === \"assistant\" && ASSISTANT_CONTENT) {\n var n = qs(bubble, ASSISTANT_CONTENT);\n if (n) return n;\n }\n return bubble;\n }\n\n // Copy `s` via a synchronous execCommand(\"copy\") on an off-screen textarea, and\n // report whether it actually happened. Done first (and synchronously) because it\n // runs inside the click gesture and works whether or not the page is a secure\n // context -- so it covers remote / code-server, where the async Clipboard API is\n // simply absent. Restores the prior selection/focus so it is invisible.\n function execCopy(s) {\n try {\n if (typeof document === \"undefined\" || !document.createElement) return false;\n var prev = document.activeElement || null;\n var sel = document.getSelection ? document.getSelection() : null;\n var saved = (sel && sel.rangeCount) ? sel.getRangeAt(0) : null;\n var ta = document.createElement(\"textarea\");\n ta.value = s;\n ta.setAttribute(\"readonly\", \"\");\n ta.style.position = \"fixed\";\n ta.style.top = \"-1000px\";\n ta.style.left = \"0\";\n ta.style.opacity = \"0\";\n (document.body || document.documentElement).appendChild(ta);\n ta.focus();\n ta.select();\n var ok = false;\n try { ok = document.execCommand(\"copy\"); } catch (_) { ok = false; }\n if (ta.parentNode) ta.parentNode.removeChild(ta);\n if (saved && sel) { try { sel.removeAllRanges(); sel.addRange(saved); } catch (_) {} }\n if (prev && prev.focus) { try { prev.focus(); } catch (_) {} }\n return !!ok;\n } catch (_) { return false; }\n }\n\n // Copy `text` and resolve to whether the copy ACTUALLY happened, so callers only\n // show success on a real copy -- never a false \"copied\" (the original bug:\n // navigator.clipboard was undefined in the webview, the code fell through to\n // Promise.resolve(), and the UI claimed success while nothing was written). Empty\n // text is a non-copy -> false. execCommand first (gesture-safe, secure-context-\n // independent); the async Clipboard API is the fallback. Never throws.\n function copyText(text) {\n var s = (text == null) ? \"\" : String(text);\n if (!s) return Promise.resolve(false);\n if (execCopy(s)) return Promise.resolve(true);\n try {\n if (typeof navigator !== \"undefined\" && navigator.clipboard && navigator.clipboard.writeText) {\n return navigator.clipboard.writeText(s).then(\n function () { return true; },\n function () { return false; }\n );\n }\n } catch (_) {}\n return Promise.resolve(false);\n }\n\n function bubbleMarkdown(bubble, role) {\n var clean = sanitizeClone(contentNodeOf(bubble, role));\n return role === \"assistant\" ? htmlToMarkdown(clean) : (clean.textContent || \"\").trim();\n }\n\n // Inline SVG icons (currentColor, ~14px). Set via innerHTML on our own buttons\n // only; the markup never reaches copied content (sanitizeClone drops our nodes).\n var ICON_COPY = '';\n var ICON_CHECK = '';\n\n // Flip the button to a checkmark for FEEDBACK_MS, then restore. Idempotent across\n // rapid clicks (any pending restore is cleared first).\n function showCopied(btn) {\n try {\n if (btn.__ccTimer) clearTimeout(btn.__ccTimer);\n btn.classList.add(CONTROL_PREFIX + \"-ok\");\n btn.innerHTML = ICON_CHECK;\n btn.__ccTimer = setTimeout(function () {\n try { btn.classList.remove(CONTROL_PREFIX + \"-ok\"); btn.innerHTML = ICON_COPY; } catch (_) {}\n btn.__ccTimer = null;\n }, FEEDBACK_MS);\n } catch (_) {}\n }\n\n // Build a single control: one clipboard-icon button. `onCopy()` is invoked\n // synchronously on click (so the copy stays inside the user gesture) and must\n // return a Promise; the checkmark shows only when it resolves true. All\n // nodes carry the CONTROL_PREFIX class so sanitizeClone strips them from copies.\n function buildControl(onCopy, title) {\n var wrap = document.createElement(\"span\");\n wrap.className = CONTROL_PREFIX;\n var btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = CONTROL_PREFIX + \"-btn\";\n btn.title = title || \"Copy as Markdown\";\n btn.setAttribute(\"aria-label\", btn.title);\n btn.innerHTML = ICON_COPY;\n var busy = false;\n btn.addEventListener(\"click\", function (e) {\n e.stopPropagation();\n if (busy) return;\n busy = true;\n var p;\n try { p = onCopy(); } catch (_) { p = false; }\n Promise.resolve(p).then(\n function (ok) { busy = false; if (ok) showCopied(btn); },\n function () { busy = false; }\n );\n });\n wrap.appendChild(btn);\n return wrap;\n }\n\n function decorate(bubble) {\n try {\n var role = classifyBubble(bubble);\n if (!role) return;\n // Idempotent: keep exactly one control. A React re-render of the bubble can\n // leave a stale control behind or transiently defeat an \"already decorated\"\n // guard, which is what produced duplicate rows of buttons; prune any extras\n // every sweep and only add one when none remain.\n var existing = bubble.querySelectorAll ? bubble.querySelectorAll(\".\" + CONTROL_PREFIX) : null;\n if (!hasCopyableContent(contentNodeOf(bubble, role), role)) {\n if (existing && existing.length) {\n for (var j = existing.length - 1; j >= 0; j--) {\n if (existing[j] && existing[j].parentNode) existing[j].parentNode.removeChild(existing[j]);\n }\n }\n return;\n }\n if (existing && existing.length) {\n for (var i = existing.length - 1; i >= 1; i--) {\n if (existing[i] && existing[i].parentNode) existing[i].parentNode.removeChild(existing[i]);\n }\n return;\n }\n var control = buildControl(function () {\n return copyText(bubbleMarkdown(bubble, role));\n }, \"Copy as Markdown\");\n bubble.appendChild(control);\n } catch (_) {}\n }\n\n function copyConversation() {\n var bubbles = qsa(USER_BUBBLE + \",\" + ASSISTANT_BUBBLE);\n return copyText(conversationToMarkdown(bubbles, function (b) {\n return contentNodeOf(b, classifyBubble(b));\n }));\n }\n\n // A single floating \"Copy conversation\" icon, present only while a conversation\n // is open (so it never clutters the history-list view). Pinned top-right by CSS,\n // clear of the chat input at the bottom; the most-recent-prompt sticky header\n // sits to its left.\n function installConversationControl() {\n try {\n var existing = qs(document, \".\" + CONTROL_PREFIX + \"-conversation\");\n var hasMessages = qsa(USER_BUBBLE + \",\" + ASSISTANT_BUBBLE).length > 0;\n if (!hasMessages) {\n if (existing && existing.parentNode) existing.parentNode.removeChild(existing);\n return;\n }\n if (existing) return;\n var bar = document.createElement(\"div\");\n bar.className = CONTROL_PREFIX + \"-conversation\";\n bar.appendChild(buildControl(copyConversation, \"Copy conversation\"));\n document.body.appendChild(bar);\n } catch (_) {}\n }\n\n function sweep() {\n var b = qsa(USER_BUBBLE + \",\" + ASSISTANT_BUBBLE);\n for (var i = 0; i < b.length; i++) decorate(b[i]);\n installConversationControl();\n }\n\n function boot() {\n try {\n var target = (MESSAGES_CONTAINER && qs(document, MESSAGES_CONTAINER)) || document.body;\n sweep();\n if (typeof MutationObserver === \"undefined\") return;\n var obs = new MutationObserver(function () { sweep(); });\n obs.observe(target, { childList: true, subtree: true });\n } catch (_) {}\n }\n})();\n"; const MD_COPY_CSS = ".cc-md-copy {\n display: inline-flex;\n align-items: center;\n vertical-align: middle;\n margin-left: 6px;\n}\n.cc-md-copy-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 2px;\n color: var(--vscode-foreground);\n background: transparent;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n opacity: 0.6;\n}\n.cc-md-copy-btn svg {\n display: block;\n width: 14px;\n height: 14px;\n}\n.cc-md-copy-btn:hover {\n opacity: 1;\n background: var(--vscode-toolbar-hoverBackground, rgba(128, 128, 128, 0.15));\n}\n/* Success state: the icon is a green checkmark for a moment after a real copy. */\n.cc-md-copy-btn.cc-md-copy-ok,\n.cc-md-copy-btn.cc-md-copy-ok:hover {\n opacity: 1;\n color: var(--vscode-charts-green, var(--vscode-testing-iconPassed, #89d185));\n background: transparent;\n}\n/* Whole-conversation copy: a single floating icon pinned to the top-right corner,\n clear of the chat input at the bottom. Shown only while a conversation is open\n (the IIFE adds/removes it). Nudge top/right here if it crowds the sticky header. */\n.cc-md-copy-conversation {\n position: fixed;\n top: 26px;\n right: 4px;\n z-index: 30;\n display: inline-flex;\n padding: 2px;\n background: var(--vscode-editorWidget-background);\n border: 1px solid var(--vscode-widget-border, transparent);\n border-radius: 6px;\n opacity: 0.85;\n}\n.cc-md-copy-conversation .cc-md-copy {\n margin-left: 0;\n}\n.cc-md-copy-conversation:hover {\n opacity: 1;\n}\n"; // <<>> ccwa-local-env >>> +// <<< ccwa-local-env <<< + // --- Inject the thinking-display fix into the launch args ------------------- // Fire on a real agent invocation. Surfaces signal a real run differently: // - the VS Code extension passes "--max-thinking-tokens N" (N > 0) plus the diff --git a/media/context-icon.png b/media/context-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3cc6398d85c7f7ff0f168e77e224b76a268c6145 GIT binary patch literal 20304 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 literal 0 HcmV?d00001 diff --git a/media/markdown.png b/media/markdown.png new file mode 100644 index 0000000000000000000000000000000000000000..cac13e37996f16a5a06db6bac7c7a77549c857ff GIT binary patch literal 63831 zcmd42Wmp_dw>Fvt3GNmoxVsFlgS)#+kip#{1a}L;f(#xs1b25QgIn<6?sn#R_xGK> zlV@M&_xV#7O?OuJ_T2jD*^&S1_PguU-=&z(fC2HtZ?& z>J`~5SqU)>FQdaOWF3u}#etV76do}Z3^;OCh0m0crfPI_JIwSrJ(S*z%p*UNKX}Il z=zbxA{%O8F^kbSyFbyY6R?SP2T@eY!k!n>`6$kb;X7!1ouO)g98|Nr*FwFh&Zu+j9 z#Yx@VbHzbm#f`r;-f~0KJAF}1Y}NlPuun1@BE($tm9y( z>3i*9){_}h%v&=8w`y%{3&n>C;BdHKQX`d;x zd&R~?{BzEKOSKfyIdECwy5~9d*^yhb?SuQGwL}xYM_^4~&XQi%{Fhe+gqdz(5dE83=%R{ee$Pmg4);60%7K1P2iPA9t51#&8sE>#` zp`unkWjH^KU8U_so?cAKEW(Y4{{T#A-yRXA-8Vt)SonczwIu3zA|5yr^KsL|_;0*zGI?$lfT+|otr_J7*# zKaeVyH2H!7TDUy0sUTi1zmbxZ1lwvm&wC!t=;I(9tYGkP2^ zM@C}P%Dr(Mh$ETrF#c+i`vRJ)Br!8HGqJJ?-`cVu771A1nuv~w=v?t!ixqi!#Fmwn zh0o!kcBHWiA^k5l_-`pX^g+mFkQOUtambeFnn{o0fcphNcvUoC$^Ha~H4<#Su4c)K z>x9s6s1`!tdaH@|kLM$-v1oMs{CbNYIBR{~Ydk9yFhQ|sgQNHem`onb(NR&O3X!CL zIllons7DJMWEO6_+~~~z)E^Q!yI5yS^ubE8#!C?sw70iM$H*95R#vuXaDKi$!s~IE zR*Ox?VkiJIYnYsy(>~X3aofXcZEFLo&=ovX8MQ^#SWQrfh!|%v8v@7r`{B>yex)#K zQ@u2tpP&0YyPiKtnspzlwT=u8$zsU^006c3&>Jd-h7|!hxB9!|S?kBgEO)br*eYj2 z(5vA1c=Src7MdgO;WcZy3yg`k@bDX*K``#^Q=hqRjs66g6A>0&eTItfBC zfB;cWz`afWm&j_M;vq_;2?YxaOVUevWABTWhQ_aWk>`)6R@fWTsquO_!v1U~ov(24 z@Z`*-5qQ?IX|wV3WMG*W+I$=MRU+$Euql|y$X=PAXTU`|B8INI7WB1$G|Ct5ipCenN=j}VNq3Z+Yax(ZRjexts{$IhW$$b>8<^Ac^+CmeWA+SPbY8(fv~~5L|c1?QfAs8+&xXG#;5D7}zZu#mYHf zdKI8B`E_!FUfomYyeVZfFqoi%JrPaPC?1X`yD_l3`abYsX>GDdkqALGBO`;sKe9+= z>APt#j-*;zIFczX>=&l#-Zm&D%II&&r@0{oQN!w{vKUgWQ;%XuHWwBa{+`LYlt*IV{zqcDy{dh=FN(4W*>uuaA~!5pl`0;n9dAT8^9L zIXEBh52_9j0puW{xHz>ct;ck)K(A7U-ySxoLcigQ#Nvd=OL@C7LX~Mxm~4Dh#AmZ` zmDUc=lhvfV!kq@hGlF;T=+h-K7$Ln6y$;#8N{QKQOE}#cX=eYj_eX~}Q2dV#U8)|I zL}rykXP*Vrg(P9v0Y66OfJp$RGCF+PdEm0O599bi!ojZ1@LsI5fI0r6OIfYC=(R*No zHkDyy{kWs%QTP+Wov{1Z-!oKfmAWKC1{dmVwcBJq1L-1HxXIn>9oHOGN+LzyQ;5oG zYNn_{A>E0X!%prLV!hPrT~m8eFEMPma&nE33N*7 zxXd>d$3Ba&-4( zTn2H8goM)N<>hAz)+iDoMR2il$sWIxzJMbtBEYnW4h^y4vkLYa`J2>;lWFett^T3E z;cfUv3l~Je$aBzXYY69P$A)Pd&}cq@cDUBjp~MkO50Nm5Kk0aRZ)PQbn-nR3xM-7s zl0gXx+1u(w4HhB|<_+5pQVm}|?0kHtnL@A@?vz~)(-q=*CV3S{jqr9Ua z!OVS^ZG5{DNy3I{KTQm#iDSc1rS5BpFh}HN67iJ&Q`S!2H%yR>AQfy$`p1Riwi_ea z!$F#a9VCGNTcha7yA3BhM@PrK);h=bJE?nPbynxbhIve!7EeF+|IRr7sZ31-gO>D4 z5M6=Q^3-qqBREIg47;`EiAR}%TgV+OWAcxRU-N0kb+C*4H-t_1=Snph={NEa=E-}b zc&+treGp%m#^mSLjvF`8_MEJb1S4-E9+B2570BY>3n3k~?)h{)3i}6Qn<>JgVM=2} z_#u`OTo!R}c_RHK5NdLdp@qeWIxB%;53%2BLv&{a;=7~2*{vqh>=Z2!9^Z1je8kOy-g?VJJj6&Yii%bXotTwQ}B#_9Z~d(ko)B@q1A zGik>Iq+E1W{iUBmt_^0Redr=F zLpU*Gs-&{d3k4oXiv=z~=uf;^`@lNrgRgm0`W&@b;#V)9m32#Wn{aNg{!}-257Pu} z*~eiDwv@bm1pP@ZlOa&Z)|jnB2MWIcz+Ve=L)lul+6g*f3U_4uSpjmHLcv+C!wVe{ za*W6RjOYxu*HDp}h@nHsM3v>ArKMupD`Wsns5vw-lE7A+V1zNr_(hw7=SVsdbDI+i4TS7{-)d&Kxt=*DfRMn6@1&1mhX z3uM1nSBr^>1+B{c+S!3W)2{>JvKTg7xO{*bgM%aBueNO&5327T-Hyl6qUmC-&3uz9 z?T;TnF1149NCYToXyDfZ9+;u^n6*pj|JUo5rss4X5G-tL=iig$ZMa=sU9UF>;s<6c z^b<2PZU4XCO(%!w)mpz^AMx0S&}(RDq#2m%l%&VR{9Q&iz(K7`gH$)3CD1c7Gh?z1 zt;TwC0-qO5;LO0YWEAZ|f4XY<3skRkGDwIF5u3>?y?Q&F?>{24>Mt+dh=qJ0zxB>)|`JNQ4r)CpVr4*`mm z2;KkAZwz?i*QzqI|4!4_-{0NS14HV6iHQtwaQzdV9}&|Opbkc}uwSb0OI8;ChBm?K zF%TkZ4z_f8+Iah~5PS_qotvobOeyYr-$Ql;V@~H^bqG_e-6u6FEkT6+@qhN=uW3v; zptkZ!fP;nYTw5cxU##tdCXzUj=iA`WjG>w6zfG&~O9}z1f~aBy*BK)NL(;hHCP^L9 zH?v2SKT&4#rU(lfRd3j#j_BOb{}Z#Xup9pg4h7Kxr~!5S82^5i=O2v`fA`0{3;qXL z%*8^v*g=1_S?@D70ghu zhJO7DjWn9m?SILk@{f>aioc<)hJm3Ws<5V>9z*Dbqs-)4Tt?*SoyT8GWMcfo6rU{r zf5jAxcxcD~#_nBh&OfqR5Y0bU#6=E=4(k_Xe|kU^+GzsEqC$J1!|C13pODK%#QraO z{Y=b=uka7O-iPY&kv{tTX^l?U47{Kvt&*c6mqzRDAJ+0G9jT!iDe^(a6MvE2`=BMY z5=C3H?eBS!+u=1mf8I{}=#IGj1vu6`cz5)tApg)$4>UBLQBD73Na$l+?EmC`oggQV zzeB(TdZQ8TZ=6G=C4eGbv0Ri_;NPp@feii?N+xdz8v5qcZk@BSBOL+6?7g@&xH8C>^*Dwg%a59-6iLlblJU_53C0N@D9RUIno>cOR@ z3i|z==_LX)3YEvv2A3v z6Rd9l4KFJZHCQw$a#w2ZNwmMJyE|Y!N5rxEl9wE+!YHN5 z9=C&$gF{Yn6Kkvg@FJG+@DT(H2er7%{>Rxno*N#hQI4s-kas%F2jcJ+6qBbSG|M=x z#+N@8+$N`qUQj{bUlpWZel&13_PR*t+2wH&uz#H1vaNQ!K0p7VqvryIgM;&# zz8pA}O+2k*1C!DAhA6DPmD>plkJX_a?;bF57LCiQLirr$)5)l(x&rl*v2 z`+kboU5fQXmiH^c6T-OD)pjR!cSPI)wd$Q>L7}1F!!@`EvDNMx6AeTUUgoQF1oo}$ z7ru;z6&k$YMf++No5dzM&b8>SPi1)6XlyC=8t&Bc8LAp#=_&%!p|nW5tCOE!s+?uH$GOx>nt2L$PUpRHdm6TIHY9Uy6hm7>$`?AeOL9GlIg@<$Ns9b zm9|3O^MPfcxa(IOZn+l-9d7TNC!l$QKK8wd>yk}A88^pN!l}t6WPy&J;xdJ?(d(SI ze#bzp(9$Q{D57N!o7W%X?4gq2j^@JAHqL(fv8vtyDL}l)^uCv%@rX2zwp`!|gb^|m zs@LGKLRLRVLQJfpt4p6=LLvmZt6=OHhwy)jA9@5*!`O4-o>L|zgldh%nhb= z>Cla&O@@{w3XRVsu|LczY(F$Y4JZp_c6yH`5*>E3Ev|FnOreyd!pN5tQEMmk?e}O6 z9E}B!no5!2&v|TN_47RUswV8tF6G$1WfiMn`_-7k^jm9fzsEk&+c^U<*&$rWd-!Rr z`s?fO;`DJ(0dF$v-{*!>#o~6AnBLy4EAc!LWo#BCgl0r)PEQ`uTz2i$t0V6$ZWUx_ z@ZeS(R^^|o(KA%d2(KnB7MMpLG`ijM1}u6b$Sy^LuT9XAy29{}-*C)sf?vL*J&qIk z=R#pi^fX&^zMSA0(47}l$@}Pz(BHA7;Zi6-3hkC}mR-^BuMV`8OO6(5-gZ2m%jYK( zJs;yOesHa9*{Hgj-Qg7F;;+1CwJwOv6$4wk!rBTCi;d495jUFze~hzendQMjrWAk{ z{y4r;?6QalJ0WqL`%XHvMEX#5_7Q3o8hXfZ{N_X5fJ)2O92QcP z$-z_~5#_yC-uxjDv~` zp(hu%#wO+`5wk1~1cH;KS*6PBEv!G!REt12F`f`YFja+l@n7V>A8 zS@iX`$6rN@g5?Z+k)sv#gA8Zl^QV)Gf|@DhB349dH+Z61{xfJYQ$ef=3lvDELd)yc zl`1BPtIF!x2pgZcJoV}>;)#jML^@3VS=g6)?jwg8N zAg2O&M5e2ccu=LLjYDDsVY{EDgq5(! zwm%|b z$w-@6&o62I$?$>v{^HQsmV@Yw;TjE9QdB^?klr1Yoj)=3NaTZ9c5<0|t;*W31x{kH z=iSUT>a;-Ju;f6<=;Zjf)sFN8s}F>bwV5;da^v06tXJH!V6D7-$2?`v)AQbfvvmUO zlFrDJj)9*C@?x2dS}iD*t0U%}q~GZF36$J3?tz4!QSn9Q(9JC|_e=lg{p?%**fXll zMGT3{ORby0X|G0K?CE%0cZ#E6HY&l0dlOClJm=C=Yxqa2a`CS$_^|m0MZ=ybw25ae zsGshAdJ-2wiju~d>?y=$G?(DDu|>C(Ynq*jnG456M~7k<<;41`-Da1^IMP|JRQAH|mZ$BfWlP02ZD| zA-F`JpqX#(mtHLD*CqaqeQzufB4f4N)%7Bx?t@BiOtjdg3KpnRR8kigWJj`Yr!EDw z$4+Y3myRu7y{s@=75o+)OM)AVNd+-b4f??d`0nf`RmQPjqMnqHCW^cFb)S6(EoiLE zqTn;U8Ae-U7(a=C9zaba3A!7Nl{x%iZs&%vJjALjw%15snWQ4$S9*g-!7iN=2l_a; z)C?QGLQDCGMx-$bOwOu7wi}iy@z064y`{3igHace5wpwXT$c4l2vRdJ3Qk%cMcOT~1Hx{FBYwjrWZ} zEl`th#(chWDs(k5C-&$-+jQZD1LZ`@1`*ER-E#RLT+t_0);?e9Jp-Fa01Hs4#I8>w zfhI_@P}AZjEhyCaqS{c}F<)dNjsdI{G0OS_OfdC!wW4xe4_U4SRYeZS?0$vBq&Qys z!Lh5|bbrkpLkg(4!1F2&f>wr9V$g0FihK5M_*o`gg|n6ueBns5qni$oqDLQ5{x0Ly z;`F|SqJ3LFi2iJUlyakGDD~X6IUms497o`C zY8@WbCeYm@>wY*;$7pf?IbKM&ke5+$(tvHFtMZgiB($KG!XWA3ld)9{^fzYiLid-I zFCIM`KKz>p3!1h6_bhc5mYumht%Z3W% z=RT;EeA`#(dbap{FVUIoL&{s<{jE;fboe~E&YzT7ZNZRPO0o|1@av(|5fd|(UEbMx znFjHf7GWrJm5uRA-12P@rsQz37*d1lT~WVu5XGbLW#&~XW_@fFqkGyTv*i|7;5ro@Z)rIuGkYat``58MnyPR@%(xcRvv7ujbjEoirA5zlnLe8zD^H7zyx&G57-moUELr@fuzNH?SXK@Ur6&h3TTiZHOoV{UeMv4mTd8`OwZ$0NA0VV4$?=E%!YiF?QOqL+=jYmFsmmbDyqzN>Jw zXF{k&V*y?oYtY?T!FAwm_i?XM`C21-C>JIUaIEacNdo;ij8Jbo?PdCWu}fAPZ7JM* zN8iN3?OCHW+@JM5?$wi`ai(!+C|NZE`%wu3Z+f%x#6sou$zYj|@1v@b+^d&X9CO|$eS+&Xfq$1v$+|?l1yW_}~@oXfN3;;{K zCCRQ6RlE#jdJeEHe|CQiqA3E%PU>5YOB24Zs4q!n30jc5Nzk?4$8?QOt*q-L#D(TK z!cQOR##R}<#oE*VCO!vMOxIr7`kz%n9edo}z~4R4=lxtncs z#XD_&1oIBULv=*2f&^fc6c1FiyKqQS_k|_mJ$?*3`Fj-u zmcG#8j$gl)(T;HN6Vk5*gzwggCA1ll>!PJyoW>J9o*AohyP@I8KmY3yoPSHID9Bxk z1ld#)kw{Me`P8=9eXYhm{$(lGaDe-Jn-_eGCrR)9*sk^Y)+g_iUb9?^Opim((;AR_ zo0S317qkm1u*WOpm$PUe+LvSL0h2RPlY@>q^RB6|P(pWNol_%}V^{i4&zI$p50aOd zi(g({S4KoOA**`&=DK^o8`m}}7G8tPT0T;BzsBLNT8oWGFZs-{LL`ao$o|wi*?y0u zH2F0ztA&Jxm__>GL8j!X2{#x&BHij~gaE9isTtBrx{>%XUG%psMXfLZ|#h)Xym_T8sR?ogRRG9lM??RSBo7+Nzsna4W}=HG~5ypQXf`55y> z1bdFkU9M{ppIjt=UpBD6OquXzuZD#b;L3iWKf_T%t}bQ%V6rdmX2|0yJqAx6>|<^qf$wIkABt?n z;GDAGp`B^V>rRG$kn|(PvWJ<3?KWsN;+@l5KkL`6zurV*V!a$44m-K6k(Vou21me5 z=c?ovz$--eJwE7dQpIk^Q%{Ru`9bKEF+UfJQ6Xgwy{-Grz@Se*+3v5;tAS7Mdr;-& z*u(cV<=28a*)}709;ct4YI{`ax(xCsUUBU3SAkx}Q*E0p*^ng~+;yGDrBV?tDUfo2>FzoRBVErji*Lx1Au%txp&%#%v|=eq8VxGQ;&>nJ`8y+j4ECk z{0$o@R3(e)`Ex40iWFkn_1IOyY}iDF=-PFT4`w(i+GL)-6w{h}gBOh%`D^Rj#rRW# zarZbGZ&7?KX!e*;2R3Gaist-;*|Yk=?u99LLl5t`2NQC93AP=6puk57g5ie5OD^20MtxtLNVlI^C5KyS1!M=FKl$)l44_@-Uz zVorjI5%0|LfQun>e4>qJTY;lu>WgYJ)2jY)7R3!C!y<|bvaK;v(9yc#t>1|ES5{i^&f}m>V>l#;}s6?>-geHzr2JL@&Oue&U9L*Uij--$j+#3hK;z5<~$cr=;eh zdSMcFH)}`O$Qe@3P(qfYzE6#NGD#KOzq6iG&-R$ZC8>c@jJRjZMJ^Ow zdslOI9)CVhueEF3v!NZ^e~{fY17GY~B@3VTo}5Etz|WI_Vo)WoWq z$Oi@(>rouW`jmK29oj#hj&B)o#Z2S1y}9LXe5>Wqt*(0UJ^nm5xB5sriYitJneF~v zHGwc}G*4TQ%xoU6|q+#FrPyB#s>DMeV_ub_F&- zPboPFfSQcadKzyIq8PoY$y{v+vUS7@fypJzT#+vAX`z6TrvB%#chpNp z-A^?#-3Lu(!pjtj-{k|0gX5D93P1gRx3xP{1iSR?En8MkvIaP~aoQQc#tB zXKA8Q-G|v!yZ&H7=c7v{DiXRNUjbvUdiT(la3X_;ezQdrMRh4Sn!q1$6Yww#q>26) zR_+J!2WzRDOR4vukX_P{K9Tm=K@Z4lAt{oC+0t01%9*01l^rr?)X3`$!Gbnr8VNmx zqi$NWomZrK%Z9;Io2mQD>`7&ELSU)nL;w}{8@;G3A&we8Wk${2A-hc{ZZr~Bo5{~= za@wh#Q&)~Ots`6YQ@??+i|Zp0w1clor_$#2zv?$&+YTDnF~ctC%5gTFk7qtqdN;)6_vKt_MLgCo69q>uz1TYpPZo21e7!QB0#YJo)qaRrQ>01E zz*o$sX~%e-bATgzwT`I6P%oruUq!?7K7WAu+tQ15!-&yE_4);!SJp}hJ=b1&OF2$N zYR3~wz8?b!x*zVY?vN}w|G4EenqsG{nHp(BPZJo%Y_S?&9?#}7Y~{`@zU4f<@JLb@ zHCpHuL4)Tmh1!6qlgNebZ+GD8a11;;j?DjV{T;?-ljm0x>BvmcNIgmG*{RTx)6pn< z`I3I9GpkSx?VC!IhZdq!Jr8>v&G`3_fX6JSyLmCH4@9((t-d4|zF~7A@QzZ;S=MCp z76FzA=>7}C=>`!td)`l*a^Fko$JFb3sZoMpeDUY*_V}c;bJmpo$A{~tx6wI!aPdEg zFhR9*#EKUo-u^*P@@zhOf^r%ymO{!ET^aiX3P-vni}vbdk6EZ*p1C9(sWJW7XL&o9 zl9%RUr{zNfXu*yh#CRJ1jEPq<{ zfq~6Z2fPsdxVmDJIOjw+7Y1Z>?#`O^Ns!`H<^-il5-L^mlj%s7ELfS@ywH+edSuyl z)5_$$i_zNWQ13huMyj3uiENa$e1HIu7JwrjGR~jns;0%W(}~42@)=*h$d=zBWR0!+ z%U2QYi2Q{@XhRX_--4w~Wz!1&z~gtW%iBB%{BSC2mpzY*|8v%C8Kh{13o?*7ubdZ9s2hU-8rzt#y$&53$*| z2WK0?Q~S_wFV5ri5IvX-;_o(ErG>;2?2hcGhI$Z>^p~cFC|Rt37Gd`bFH81(O@!T| z?ZNO8hKZjTt30?+Q-;hGJI3spFN|xmTklB4hTD1;Fvx{CW_UDP;H_xEicEiJOZ2Y@ z@J3!&t}1)@=&|DW`F`PDm)>)8U3_mqEy+u3agkO@x&hDjV{+Y~|K@jznoSP&itH-; z-u=XEpGUTB=V?E%2S}Vrs-yB~YD-!R=K$kKJ;&7*9~3|4je^~rBYy5mbe6p+tI?9p zXa1a2O<)p;@#1ZX&z0GJozAS^q=DwY#-%lWlSv;rZbbt^6m7!t@WJFHF7|ZDo!MB{LbEMVs506XeuMcnfO? zl4lLcY-Q(wa{u19;RT_;j&&}3!U;A$hkl5kbVvs&V2L^A-M^J<$v@5j#_lj6NlVW! zSkG#14w$Wo$L~E!w4tK&nZkTwz1n5`v6r9iP0SqSSS;x*jcQBluS)7~O)k}|+j%m= zX0y-mmBKTX+ICGU1l2x7p(r+i-@Fa{dfp|l23iAnsFJ!9dsX0p&iPQzkth~6s|v{8 z_*5b5>QoDhpX8+MCCSblr_5V-;%UWc<>P_)(UjUT!IqJ)DZJvoUtRTwDzg4s_ohxu z|Nh3jO=*3^X7jXtwlak~0=iXCGX=XrN(F(QNaz^Q>P$gH_IKuo)7w)DAS!lAO!Z8n zS?#0aHI8IBkx?MotHM0Ye+}M|S~~nzJLXWX^7FWH>m_gx@cooGU+Y)Xuyl$yHmF7}!z8z>Zhmet%&rie*i3v;{vd^Ci&8S377u2yq zaD%oFS{NXJ<+T;Gs@6+KYv*}&?^sYuf6#W^8?t zeOaMLCmojycd})N)}U0Rb<|t=vl$8rMhblcW)!7aJeZLVqS{fTwycN%bflu`W>n@! z$UHc!0U2lCkfXw}9AeiwqeN0PA z0z+4!8E5W4Md$snmx@fmJ5dXWBBpk!{aB#qw47Qz)m2Fue;?kR2=y3+b*8B4$tS(o zE6WUFhR_ryze~kee$0od`yNj6vDY!%y}R_r0M+9Uc&+FwyIRae_#` zZRGD~7nn}VcN}3AXni~*`IE=r!z4fJ(`^`~#SLL@MQizBHF;aKdnbPC*ftjZkb+N9 zEYY+_TC+(V z;~OB~GjOM)l=DsfM}`VEpY`!>$qY8=yg=AdsHm5v)M|k6w$mcSyAF7klUeN${e1Kj zk#U;6MwxM|B43o$(I%&xC?YYr=hw;)f+m}T1R4t3jN0>D245H`ud+9(EZpB4$#fxK z$gv;fS3AA9!WAj#fjLo{Em!KJR9u-wU6#djLofNYCp*5W7nr<}6iH<5>!>%s-N0$Z z+Y8HUW0bM`391iMQCGQhBv;iY>Z_i;JUbnBCg^B&NM{UV>OzkmL|0&jSZ`Z$RxH=f znBVheJt7r*R4IxijD~6@Hce75I5#@18a@n|*?mNYqnMA^918PfuM-f?5vwLx>_f$s z#=_{xM>=`~3e}qw$EL$Tg=^M*AvlU7R9TTi0;sS4lS>$&9kIW7jNN@>kVl*G+~QTc zIRXtzzSB>9bn-i*D2Xyp=~zD&{4n#dqFAm^-^uPcV{%0?K_XE;j*XDBtE}2?W2?zFKhiln&4y~KB zZ`>={c%oKQo}J0)BfbCe_B|)5Xv3z+##w`M*(U_9*8CkziD#>cE2*WLcWHNl3kiC$ zUsMioomsO*tbIG5$aB`AeWj4oK>_SI!n5o}9zei{h_5 zt$dP1brj!#QQlR>_Q#ym`IXZTD%#nR8I;m3KQSPAt|gF7THSlG;Qo{zo8pMQAy+SU z9E8aBTuyB@+lZE=r+rzxR85WkXom2GiZxGo*$=E$?TC0B$k1Ln^NHx_v!8N+Br}@n z2M8<^zA)GMICZ*vXxV7<7B_9YvHNgMBog3B0Hj2~|AR=7C8vMKUeilzEiR)?z%0!3 zlcDfY#&O&7_jp1{kkRR4aN|VmqsA_+c_cx0T}Vbn4yjT4GCH{fe)Y*5GOwx+i8j5P z%?C+(5-=z1jrY6ieE3DxhshWc!0?xNuIq<FMhmeXPJqlBA2i7V5tkmT$aXAhK=RtOM!|O;)mt# zhx4jYujna+viV$#rY4k=Mb>UjD4G@L_7MT+J$iL!gl}-~Wc5E#djG+T#7H?x!RDQVfp{#_J9M+|N0H-I^^4ScFHC1Eh}>C%?D)MPi)QCMsKTj7$kG?gd9FNXf_O;#_nW ziDL&wT)(K!J86m+Uwks#Vd)lmP?z9~HKCZ2pEGj^Y`E`|pnED#W$K05SgsF!iV8=?hCvF^3}8WXsQmh)QRjvHxf9OCAA`b+tU%RnPAMVS?}sc4^?yCef!2 z0t-rDP*rn-@K*;mr0JZ-RtpJ4{9~2CMR?8bZx|Bz9D8kb&UY|w7eSZqc*8Z7i(tpX z)fR0Ym)3@!zO|Q5ngyZM(YQxxlMPkcIv2 zF_zJv!n!}MTGz;b;8*|<*zR5dx7*d7kii&ZXNX?s(%zHYOppRs2#u799XgR-)0zz9 zO(}w)dq{>*cA!6To}Ya6Zkyum#Jb#9&8)5^u$3`W771*;x&V8?swr0ZJ#AslLYZ<|z zk>~G1GJ$DO#0 zMd_oFlhfsiI$@*tLZ42@kv)5^;>WxmpIix;?n*SxMg-P2V}tA9U*vMsMLrk#1|@q2~2edj4&XC?y;1@2K*7g3)G}*~XNkRnxPn@VHS%I|eoQ zRsCvonk&)|5PE57XNFXx6dqjcpBeI0N{P~XFKLcK;-qu7Bsc2q1IJ-wRJ5$>D&4nE z<(J6R&7t0ua00fvk&%$R?^&3N)4EJzkNav|2w-Ly@pc>+*GRSHy?4b@`#sqgbx?YG zcXK28)@sb@VT;%A#I03vYJER+_k<6YG7P>mx%*-cNeu22X@0_Nd?;A_12?z5j3oEA zA@qB(ch74_c?JIHArkv{MwYn6D-5Uow@`fuF<>X3GD{^Z=q>&kI$TTh4s*~CuR?oAv*PauuE za4xK@7B4`lIdB|qxnbi?-+B;BeAj_ zB{vi#k}m?Zaof)WOHrEB4<^Mq(Z8kMb%1p!K0c%k-4BCf32j++Is>&+N=-W>)IJQ zPhp0aCC4T4IHqyqa{JPlJ9bPg5Nr0*35(XJVT};?a?$Ie#e5LNbki^@@$QaTum^*C*_O~Vm zG$`TIDhr~uo7%N-NTO$I@mqImIw}$F?Pj7BCsO(h3Ugn6yXY36!dgoX$to+*0 zmNYg~t*Xcg+~awUlms?Qi|B1(HtTl~oH;`?`1j5GRH^Jls3rYFh{viAG!O5rv3I6; zi(>BQiIiw-P8FLY?n`1Sd7Z4;j%{7h&Q9$6i*aJ4ZSK9`n&?|j+ro?!W81Lr(wfXn z8b$aGg+i~&Jb!JjX-2c?0mn|j>$h}aJFga8aP)HPS_E28PQ@8pU0o;s) zgW?x3FJLB)BmC&Fo-2l=$CP?92H$M9e|rk~5K1_r7%RFe-`nc7f>BqD`;l77q`M5w>^_ypSKy)=|s_MSy2eAa9Ltf zVptrh$ykYPz+O3@^be_7;R1ubeD2q# zD}sBIv(RIL`C4vQ-KK{tT2UWmDe1W2`7VTIXc$Tn(_eCjBBEn~jefV3hyZ~Mx39~Z z2sfBGiQl?ls`R@mKT!x6odPcnv)%A=rj3=Fm+1@w6i~+Qes|hBG8C(&s9;Zhj6dot za>TBV8>a&Oj<~wN`Z^9EeBz?%o$6XzbvxzGI-o)XI5#qrTxK`%y=D}tGOU|kIQX3b z4Smi%nzoUGdQ(Jj5$6P;)D;qg3;J?E)XQhT(Rpwx>{gi#77us#7eHL0S61#P*w+^P zhK4s~P?Ex1auZKw(Gn+ixt2=~QJARVhqwrl7B>SVPGBc1gr1eGB4dw_!oC!`eVPA& zqnxEe;Ib3IPlmApjt;OGgK@l~R5s}b#pU5yj+TYcL?0wseG^?mu5+e--s(6-GlW?^Ks zq^%)%AgcCb)P8kz_*|mM?dV-ORgV?;$_o8o!1sOWMJsKh0|tH^u0*p@S==+}w)sE- zu>H0qzw{Bm!4h9?$eT(piCeHCv`BQG)t!Vf$G`>!(uTDEZ^i5(7T=D`^u7jL4KMs4wo;3~F#8m>P2VK@{HGLcenUdBE2!X?EVIBY}#b zK{Bqm`*ooK*ca)BQ%@R~d+q)J2jzq!6;7Z3hpTf8tnAtHc5K^rI<`8tZTrNw?WB{A zZ9D0X(^1Dfv2ELL{`bzEcV<4HT~%xETD$7(wbt`10<6OOYfF}l`1-5IdSOV&)L=XM zoz%GEkXjtk!!D5~0Zw*%o95CiTHsO2uJ>WNFfZ{rTB8Sn7=PRpwAgN?M?7ARrbGlu zhn_&hx^7cL_@p>SyBsaBcJ2?bq`a6^zgvgyY}?ia(=s7%=wY40$ZCJD^G(u@n1hL! z|5J*YaN#2KWCxyG)1NTD=J;)Mhs>#JSC4B3~c+UVJ_(vG9Ho zXMy1TBb-hkHa_$Yi9Ij%_0`8YO_vL+#F{jyoFG9IZC;E%C4B5F)dPAeCSYh{m!jAe zIG>qjiXwEYiW+y4#A}#E#C^7s{ zsA=KGov!9aP2b?qm4|ViIo{(cE6NX09pSq|OtJR5Qb#5YD(e@KXX6L1Q|5)Q4^_1p z;<$=mT$<(2rcV^8g{hN}vXdb$pCnH|m@zfgaUq$X%-Lo?QxLTVd!ow|$WdfO;=nxV zn4 zi>gw4W?o(KSjZL#u484V?>Wh7r%Sl9r0t6A46&@gd^2>r{M|%Pb?;j79-i6?gLE7X zq6obtbKVU#pBCeE*K3_G>2PQ3gBwv{^iVEGW~Z-*LbL<#fPF2$H>KeO+Xh*4{TB0> zALq*-6wi-~pqF1ZY30g!|H4`kz?&BHg%T{Dqa&1*Tsk=G+bEird7q8+8D|-VW~y`9 zygt92tKYj(0bMFI9}8jm&y8N9hudfO(uRVHaA{7sA%f1M^Ph=k7)#}|pf90ehh zSv)J2i^;n>J5YC-OG;x_SpfKDe$1%{P|TIcwRu%p^uVKcAe1-cGg*Ez8-dQuUCN4( z`G~gJ`S*za%p?iB^BJa4qyl%%mY_IS&PY`bsR)Y>LIiPiHj1^OD=1 z%=-1r9K6$`H&~f8PPA&%@yXU@$ZLCxs14t~t2V#S#}X~;(MAZZAL|C=7Rt@COn0cbaq(}xkUZpvUd_H^drI#n+L9A>eid>=BCHSS1{akT*1dAJQ}jf`rb};?DuPUmJE;2bQ*?5*^BIUOSj;nq z(1m=EM0TPPNl3MM6SH63^+Oj_Smp$3^)n4Lk0!n2Q7|GH z?<_!SQ|VjmS>TI>QcKvZ9u@WbkV_T8w;HASXZ&i}K&W~QBdLn~9s=GG87 z5c_*|Y+%X%eF@x^?i(2Iw1Vna(Z&94z`OVd1BQGvk6yF^Ig4ALmYDSu6wYF8er1C*0EhD z($HO*Ih+{ogebIT)Yat#J`2oJcK=Vn5=7{V8RHn1Tbj@9uDLjmfz&d-7gN+Nuj9Aitz# zQzF7b@fgZ&^A;lXQf-yo7}2FKd%`Fy+Sd!eCE1wHeNo%rDpt3qdW;=G8iuC4-WYR+sa1x*wF^!xN?xt|oD%8d zd}(0pzInBD)H`L=@95vZ)zL8ZCD43YQMI14OdgI$FMmPUK~S(q_PdDRhq$azuFkh( z68ZFIRt)xGhMORCYp$eFHv!K3+* z=wECTyk4OC2@dG@;9BP6_ocl71X8b*`$dfWt>q?u9d6@PocpV=3;$bQr;t1DN|@t- z17kG6IUxE`=usoPBh;)*`n>vo>2Rxmv>rIdOm^k|H!crK<@lvTP~c9V#u$MAf8p(k zA76K8p>3OO{eMAld-E^a+Y)P0~;4z>Qa#hlxj?!Vb^pN(v~MR8|}YGndbio&?ed+XtNE6cv3PP!^R^<0eF>Wll}io*uRt`Y%=%z z+S(yuJUu;o7Zx6{o_>_x!+r@|a0h(&&W&OmhW`(Q9P1yzzfXvVhl&LKXsLl*rx`^) zmPr;3r2}9&ncZOaBOB|#JO5(i3I1W@Yo%yJ*TYS3NDP zWZO1|q1BjC%sY07hY^>FFrHpsEKw3vS1^wC+_3(*I8u z`i{!KCRwmA{H5q%HD$$rp}KqZ#_JbMmc><$MRM|(Jca8%#jHkIertF}PP+sc(pS}d z*cPL`W3%$RiyI4)oleBLCBt*>Diee1X?w*Z7v7AIDRWI4U!_8^@qB`F&D{DTM>aHM zJ2HMuq=Na6sc|(dE;9MXf}{Ua`~d@yzYE2qjyjfQrTY87Al|h*G|K|_xg@S0?1z~Cw~+=I zI{l#ZfRgfC!P`^6bVp0vSwq~=L;v5}dHWHhiVA;2mF$nMwoqqN- zS2;L_}SYdqGA%M?pbd%cPhYOscIxXk@Dnyh-W9eVm@oL6bGn zrZwkK76lNhD<}9DbersQ&9JPv2!P1QFuLngJ4V1FVBkC_u)?it=RedTN6DtxFP*u+ptlbu16`nwnz-eQ zY8bnFLFZEM`lcPiUpcEjWIyPze4jF%*N4hRk-d*b+~WX^O4e$YdWp{HE>Tg`=}A;h zhT|y?Oq%ci0!;t(9)zJ+sBUzs{hOuJ6OOl`}ss_>q+yr%Ga+@ zY^WaomTf1suA9$s1mklG6Z$@pHmihtAnP?E@$w*t0mLsL*xC%Bd2x4&XzDuoIOQm( zW14-@@M@8|ni86LM?E?&86CT)OUW4%myqGTVV~3dH$LlO^#Y@0pm01EBO&lR%oq4? zhFj5ey-0AUCvMtej{;hd1ItA;$Z$ixHya-fK$j<*xHL3rS)-Tn2O~QLg zKz~57>8gX^TseYPO1-*FE1cT&Ev`!9mN;29Zd70q9^#|sQO>T*gkqSDKPfr)a|9L ze52~rp)!D;_X?7=7ExGC(P3C!Z}js*UP{nlb^AEGlNZwD>4^DBCBPu*SDd>W(kT{f z+*OWvKyhKYDR-PS5U1St-*|6{VLXf7!dZh>9&%BtlF$7$#L|5oVVPTldg_lP(<}gg zT;dDFezG{U28k2bJfHj~MO#1t?-|W%rRFJaUh~@t>n*`dQ%8r|qm2xbv1*@tV`N_ zLnSKFi=D(Kr5DAop~gPrf5eo#Q=@)beM=n{IoG4==FS%au2Wo8)`7;`J6bD$VVIbP zZyvOqL`sPD?HcBo>ny%IML4%0upCT$XtH0Fs9SiGJZw}N#X!IVqzY2Zq51!%IVFE5 zo$J!VwgBhp>)Cn6uHbU%kU61~lT`2`g+FS`+mM#pehVv06V8PHTvFR0HZ7`PYitPW zo58h@uA%^{G@NvWNsT87!yhh0MaB>@!g6#}4VIU;2IV9Z*5*(;HPfPA8?{pvA-Ei{EFX=Xjn4`nDe6aED!_ylG+&8l9AFc~UtO&0Yy2E}T&SZ#JTG@U zI*ao6@Ggw|CQ(J_^fBOMJJANFEz!R~^e)0%6w?9z1-fQIO^p5!A$X}lfgT8?)_=g^ z?}YBGFgPcVU-xd2#McF^ELJ%^zu}&(x`0wwd>g<-BZ|$nqjz#%CADQ(7;i#+L4*@| zTo~l8zvgCDe;o6`K?4wV=l*6Ay5(ae|J-u26*Q<}O>X|7(5CNxyTNnIN_AN=>6bam=xV!pVw4<9w z=8xE7kuCbIZ489hpyyC4K+0*~<>Ls~y4JQi!vpbZTr3Ro4^O`+;kx{`^OE~lzxE>v zb-%WX5AXIc2k?w)0lh6xf0$lhDX)kUD$iAQ4NDz55*1s=7OAR#|J@RZ%?5De&9#d-6w#3g5y zH#Ujh-SC`xl)-t|n<05gd_33}?51pavkMzy_RA%WO2Y3z;`Akp!tZ3^emo2%gV*fQ z*oeWnGc-gfa~;%2fR1pKlb=L+rtw5txc#bZH&`BN>%0{ES{1vYy!5-ajAVF!fNgEqq|^P%ENNE`vEl7vCcLoALbR4mI5m*mvV289fi&=-}o+T;l{?-u4>kj{Ch*hbd6 znQ5rSa*M?~H389Qj^NK&dVi-I$g|_MHM)C_^;k1j6p}BWvOZ2EDyOH+>Y|2HHrvct zr_(#FJPkUCz&SaQ+tw@f>VOZ-99nkR8U>u3g{AEHK93ef&6zA}d%a4i z4I!{fogScTn$eBGeGrbZsBM^>ONiZ$XQ}11@HZ7wr6$(`t4$KS6au|4uRKB$>NrS1 zoK>fO@R^_VFrzU6tX&Yxozsyh54N-xGmDy+LZ=nNN=q?Lou%<*cN0yCRA_1exbH?2 zc%H(X%mr7WBQZ}GJC^Hg(EI={M$oj7@|vq>H7!bH2G=m*m?+p5unDS-R(kj;IRzIS z=*nOuI0Eag54%^3B(~+BbO!YU{dFF?{#-9OmN@hg3xq@eyZ$92Uy=8oU1t38Cj7de z2JFwxa&c=7vRNiH6bkbS(+h;W>(psV@ixEFAk&kP>7L$_TW4hPIkaQYMi59j zE>G{SJ|B)ov1!n#As>v2OV-WO_rw5JGf7(*`DI_uOeS7GQ*Bf0x? z^~VCcCBk+HjewD1$unWnEwA^kD6NZWSZq@?=GXL4_X{Anan(lfL>Ixg5~YPHIxr3k zqU0QCqa~0VqbfSbE`{WSf;c|$wED*_9*U2VKvpm_2Og7c2vh#2a|jJPI*5Kj6Tmxb zY9|*&S5<)MUWfpKab}%qbkQ19p8s2eLp7*hkuf9UqwfZ|Iq0^-c}WVZ>{6BIZ6Y{L2$kP?}StQY$ z23vN`SYOv2fO&)-OJjC30{hjJH=3iy=jZ65#w{$P|B(5IUPExx=^ZoZLX zot;dByQpKER?i2&`yBa`S@98R>Ixzpx=kELZq-QH%$O(C7sN(mdI!SxpqaDHSt*D| ztAlytRE%@Rjvm)Qm~Dt3MGKm2v{8bgo#!A5RZ|))aC~z5q5;;D8O{3V4L!ktWO@{Y z1D;#aJ|h0@K(%5dsLcF1RC+uPl_aN0dNdEAVCjmJzh-w=L=>TRd!`Yl0LLH zcb$=#Sk@{|t!l7b1;k+1xJXZny0>>9kSP!hQ#{#$b0nSt7#m1cUNINf zv59Cf1i_oHe&rX*=`%J0YBxc5jUP)Jn;`na zAN9}yQqB^=kW4sCJSu)y)`aR-Y9KE_QrD_3pR=46$+y55#5O@GC|>LV(H$Ycf6P3Q z9|Og%0}4zz<`o~)obq9N_|_agl)-@`K(8Q^KnDRx80IXumdPC%)JGU&j?y4--B z!XBk0vm~!bf0IS9qZzul_cOZs`wNH5FNVa~w3UX4uj?hB!k8+EYxL0s69CRbK_6WI zjnWXqxBxvPq1ALujm?#xIF3}6Jgmq83RsPrQjdD!C*5WT_hp;+jZrOd&2$K{^|gv@e=Hs}Hq%mnBm6D#+`jDa0dTBRi_iNI@NM3m$2~wR~C7 z9^=j(`%=f)g=z@n>O&f(b%QlO6qTL zzKJ=y(j@X9Zx0{)Qp}!X3q7WP-?pBE)J!Ngo~eafvk1Zyhx%yK2n_8{Q}%2k%JI1y zfGN|wmUxso+f0mhJH#(|4#sTt1H{x~CzxDd2gDSq>)V~xn@@tgxmYo6Bvhr6As7#k za}^FvnvytP4|@@GYe@*jfl#w0{2HHWqrtE0=?r$VYQj7`(!e!kmw* zCzNE_)@PXCNI7$Xs2sX8%}_hAAeoZx%qzveq-uWzIff7W2z=Jv`)4+pFHK zWEC!+;pJOa{IXoNios;Cu87p6yy1)>`=$W_9AXf@65-7@V&t;<K=k|aY83>l1F+Q zabYfGnP(gVxq<>vQN**LqmU+lA5d@9@w>WES@M=sX_Qg*rb&Ckfz>>v44|KlV%h%0@i|k zH+;eV;~ikhCZ>O~?ySJjPr7zSRal;!=DH zBQqx%vOgFx$NanLp>!-n3F}|{PS#xkOjLfF!n44^x1Z=+07t}*)Dq(+60$Pb#lWca zPZh+6TDd<^JnDnaL7C6=OD#)K>$Qg@+~1JPv`J)d9W&0O#XlbjCT-<mvB+FB@rh`Q}Wktl09B90u; z0`rPX!D`7>88ZV3TezZy!I3Dq4%U*(?o?31@00j@!==oy_BfjTQEE*05l2g~=@goQ z#FGSzOS8ZU(o+YDaQ8G_)V0kzmkItUTj1-bc{XzWhwDh#=99^A8w(P+#f)R$7u}K5@wFtS# zhAnzq^EBOz>suydG`J!=zr`o*mbuU)HzbXyJ=46ixMaakrCGn5`Nqz`C(xqG!h=Yt z>u<{BFKFRJL>bsXoXWi=xzv@q))QEDZVS0ts}H|ha9TL1Ll(1m*l&6*yc2~v%@r1( z2+yxK*)<{R*jSDzh*Q)=6p`NU;+aLn4P=W6JgI67Aw;jS;c`%x z6;`+gb@7NZmi?%PEf2aIw0A&G)JE~e0OjB;QB@l9HpcIyF@Rlf^-(r?zV#52B#_k8LWi%8u3;lD{% z`hvw z^r6EPN*0cawzPqpBZND-bhC<{s?$t zp)BT?MqdmBXO7am2FtNR<5b}twQx>^7%(QPIgSv?2Av#7Iky|9{PfK2;G_djHeXFIlG5TkX(hDW+3O2VWXe6eK#cxZHyW5sYgsG__Az&-3jXH@Yqjl*R z()PoG!r-?*++~2b0m#|JCSHhJvbPe12lB?I;*m^VZj1A`7sIlboaq=8ssfpSJbBykLBLjJr$7&g$H^$Bb8gqi_&@ zR}R}+4&D0epf{8A&`x~r7UV+`&#Y?v$v{P@dC(8EWGTkunrrAaXG!;zP-PE!@u8$& zvSba=c*!Eta3nI0`##jnbw?4Xqgk1}i`oWLGR=99rYeryT(D>oK>gHp3GNY&6Gbqq zBb_fXF?Op+5hKO$R=Tq#Kq z+Z_>u>;jRgI*H##*+-CN55KmaSc2I} zxPaIpXgI)_%K(9Z-<(d z+WaBA5dggITMm(tNr5L4dH-90myliP<|>ST5&eY$(~$Y{ z+kk@i(WWMLoL7aT_G;({hhgJTym*Ll`89}on=5z22Oh-~)CzJrt?pE#ko-ev$8)JA z?qa%wC(`y3`Zh#YpWRgs0i^#?g4StaM9d6unp?#+dh7xk5+P}aTB_ccmMu3Ln7Fe! zP1@fF@`Qd$`6-MW&4^i$2)GB(lIgkq!dkEgt>^dw4OC0kwNaz(pQSdc=Qsm(agj}b zIOxxZChS}*I`W4?^;D{Kv47tM`*Qz4vCNF&hLkvF_N)MLqC&KRGbQJNIk3PaN6taP z)Us1uSYUDHZX>c17!Uv#Sr1U;A$a#QS`LzaeZxULu0-(^eDP{bLOCu^X%tw6(yg{C zpoawfNMe3SjBo>H;Bzk59sElBs1*pz#L;SDwvfJ}9{fhjCS|?}%cNcpm0ky2Q}hN7 zdnNTSg=eYy?O--uiVwOPsYK0Yc_>?+MdI#`=dV-cmlhJ`7tqJER}`}8f&gH8C)vI; z2UG@S(%ss|nQ!^e{rJaQE$*R8mRcwfTe|CnuSfVPyQn?uKTVJq5*8#hh5<(3OHS1& zjc$q^F}j7ysQJ2#ZzS0ZFMLccws?z~2oeTyWwZ#E8!Etn0~HBs7=oG6J0*<~p7m~7 zU^d{aFoLSa4-PUZm0H{18S^<&NTpY|0dmJ=W7H-{{n^yd1tP}C$YgKj<`J?M zdAGuB%C0tMZwrA8M4jJrLo+PYQ%*b)eo-ri)=9&1GEEFHLT+ghDY4x<4?a%%s$ph} z&l1CNPSx}@IFX6i(;QQO@lKZ~Ji5w0c8dPPoK z{In&?p&WW%>HjTC2{*SpI9{-|O!gEJ=g9W059!lk<~D|D`;MP*I8v2V8Y`7_340f= zXmwfZ;emi4F{x@p3E5NUsPN)Ndw>G|SY_oa1!vuXT;Xpn%Fap=q|=3a-lQMwx4>Wc zKoiTtp>avXBT1Qubj+cz{+ zfst=6V_bg54nWkO0fZ87EapO`exPy;ELuo;^chA-l_LT|9BsZ8A+#ke^fD^Mw_MLA z2ahfFx}8L00uzBEmD_q~GtJN#E29rNW&D8^AGvlR3>uK->qWfvBWlo8CRks(E>t=c z$}7#NU`Hh5f3s=zs*)7~=7^CTkl_mg$V8`VneUTSqIPM2{@w>l?qIKPi_&(d$j*R$ z9j;JPih95?Kh35M(Z6|(vCv5inNeMz@`M4npgxRL`{q&XJ)cd(3p0`i2HknsE;K^-*xGkg$89 z>+xbM(`C}i5vwq)WU0jry;Rx7l{bh%Q4Qcoib2VWyEexncplh4IoqL}-H)(jo(5eR z=2GPk^Unw;b+=QxsKR`nLC2JLQ8S^H19ZUBxZ6ovq0{UUl;M%<}~?Gh4#SLF$Nbm zfCh>4sUoer{Y@G(Rm>Bax7fYTcWayImYC@V&&d-5){?~i3 zTh`?E@5psMoRZVPyq?1EfF8Py7KY@gatpQbH~U zCBX;65#=_si2A2Wbjw19!;h{+sCAD2rx=oMlcla*j5;V_;6kg}60@(hl=Rmc$U-fs z)^h{-lbm5wrERG9I4TM%tQ*w4+Dx8J`GdXo5t5m~OvL%#&&YH;N)W*KrlpEJ5j+dV zk0=CNvJWL0D(9?p{X?`SdY9BCFs1(dL8&#d@s@EAheL9JIE}j*hUn%3K79`gY8r5Q z^W7J#TYr75zWZpTyqNd`F!V5 zWpdQhD-S(s(+f>hdP$;<$+s69=C~aJ?x|{{Gzn-82h?ekO$HEn{h-1pGh}VEc~SL;a}DAJ^1XfcXl*(IWkuq6?B z?&tWv&Lr>ON&c}obD=_YQ+y7_RkE6edsKzVn-^Or#K2_vNg_L@d?;rz>RFf=1cpjOCBsQZLJ%;9HO7F6$zoQ}dK89p_0zP*swn?P< zhaSO?p7ckXXv*R--XJaXUmt&fO!MLRIMMKOyD)bT@}>AJkSwAVwIV97!pdl(L>6G- zP*tNch!@@bY=jQvcuUwM2N}At4kYtSWKtN5vZ@c7_TPJoZ$5%4D|qEXQ=h&(lLOxd zCBX>u_e5iJGX{<-#H+(jTP+GyqH_)BlU<{s{2S4N(4+Kg+2LUeHE8AAQ&t2pD9LS= zVN@sseQ%XB$hb*~><3UXdd_tzeyT~$-BxA3&}<)Bd@>na8DwZszX>a(UuClJo*w7t z)<~N`6pGn1 zR9*vql-Sf%>m%wDGDe%?hY6^NN-d25j_E$*Nb_p%<4J%_&KV}lfMC5_JFa)D>q@b%wOVi0<=Z)W#`K? z*2zGdN9js`_CqB&P`v09x?&M_YG`hoXRi{XoJi@5+q1o8xu7 zZi4J#hwR=BfuulY$SakdR8PphN8a8QK+_m9YcFR?`^rB}{HkEFzt~=n+^GD0U8~~< zq>)+d$ilMM`yDxu;v!|NC75!r%=gOmBjGJ*Hy8y69$4vgZHt(Dd5!Yxl3z@--~LF? z53!8(21S(UO1`3{HmHDYXt&9D087aqi9Yos82x5!4x&c|c-5{g$PHDFd$RQ$E|dK# zz0^}v-}HUs)rkF`jvVko98X43BO}ySw>$?!JmoY%1Li9Et8`a3yk^z}g)_h^ZsXBm zXLjP0`$L^^XFs-TJrFOhKXevleZv`(y>o%|Fxq;kT6ki=B{G`#Q;ZHRV2MAzTW3QPn z5rHEYm$|7$e`k!h$QDWk5t?_Z74yU8c=+N3Ob|aBrWA!whq;MOZ3diGD2@_JiBexO z?*(V#CWwne{jz915>RU0T>k9gMS7o6L^ZdH)G!`g7`}*}LG=JMjJES+0I=s#t`2J^ zA(efPM76Q({Sz6n0!IcuvJR4v4^DRkf=hSTo#K3cAR&NQ;q;p^$u`opqF&qMc4$93 zoXyw&TkuAyzuIF3sK@yx_tl4-t!G%egQgVgrp%j;K_=gBevcq_H@elg@Z!UxFLIIG zF_~;J;7HjASvEf>2+vwO7QO^MAnkj(CMjg2{=qWX`tKK?)}O6fGh-_YiO1YNA zEV-4%Hv7^^1<(*%gJ5FQhptBP4Ct4dkSQ>O z!&U!N3y{u2Ge>#Xg7+$|RdfLE13;Oms>1-|xY0SKhf7RWMvSRoZ00S|=2{M4?1z4g z;Cz<=0peu&<(T~taaqcsvRa;PWFo17UO_UVz%$6g6$%+1!O35^HpaaPSPX0lp>3}C zbx`dTl$cjU?%i|q5dQU^PIs9v$K=`MW^O)s(fQIniqli}g0^VDDa_~LqWX2xbNse- zKlui*8Ubms7{ERAxPb#MUsAX%W9m<_g`Hc00lJ2dF; zZG+`gH6~eA*NBY_>Y44~^_t`5aI47cV$CWI=<3z{F$s8a?5~P7zc0Xj_XZGiFTqF z0G(^)y3C>C!Oz-WfOz_X%r6ZmVk7VPk2D`ouN4Q~_HrwPP{FftdwCj(+h^L=y-w=> zvUgYrT5zVOkd5@OS7Fzuc~@|tw0zd1GN}lllb;1BNyV~I&w~Lfs}B!sa!A{8M1>{- zjZsfYQe5mXL0gh>hs|GYrF#t0V=YcFaPw1R2&H*$iui!9W8U1L93%Il2*Kt7`&(C0 zB2I!V2MePMrQ=0|pH+8U120GH*@3A2jI_C`uy8JHj;#^!rHEvefWol^p5e1tjp4Yc za@4HaJSTs2@S4IP)_^p^1S9&WhZAdSZRX=a3(>#550K76d{HUbr-2p+(XS$OJ_K{s zD<|?)?ZSxR)*o@^Jdp~c>cY^|%YzQeQv8%o9^qeyzjQbQ>5Yj_F@zW@$tqhCn*e*JLv0aUw>R)60KjU=obb#wkHD6JCtn?-SIY_gZ zrba-yQ_I)5QlbiM>!g2MIpBhH78(vtb)LS5hFG;yLB>SSFu?_8o_$Zkb$NMEbBZq6 zWA~C+a7=^-DiqTL!)8rXN@c|~D?9UW79_EkP?gZZl_df`av}%-o2L7NPmDi@6A~m- z2ZV70wj7txl}v-0j1u&y@~KFZQ~f}#Gq`%XF?o|4E6fg6ePVp0B)DwfQjlzqNX(iw zL)Aj7meEo=xb{wY2VatwV#wWh;3bI^7#@WJA&7-u$5q=b4!YG9!3dO86%E$rvmw+{ zg=B@~_widijnS22^4wvvbUJg}gus!2zb;i*U!}BLhz)5^lgeh_i7v&X$pHx-1XFi& z=}^G!tcC>&a06hfkhW!srH;bd9uKS|n&$x}2{%oCNSDQcXe^Pw*uBd|Y59bfOa_X| zge`H#nkT1A{r#Brk&_eR+xyO5@Pp)oGb*PYygm^P~&P6!wVqlE%*A zJN+~0#_Vmt6HO^*MONpLdBW!Rrs*tnem&N?jLu+5;=IodB!(I6ybK%vxfYQOLJg__ zmJN|9+dJ$-ZUSZ-BpcCiDnvwg$;`10#9tUjQ`prv`a|Ehv84?oNZRlIJ{)Qdc9?h% zgSQ^JmtaY@1Kjn#AkUf(co-s3dY$Ulu{TlF0qgRgTJw;J*IK>-Bh!mKGzrAJ%pGj6f#dS!uDUd8U2LdPo5&QF-nx4eD$LAMTjR2}5T&GW~3TeKDEWdOPl_Tvo(T^L}iRCzfqj9i4FaQe+Br9u1 z>rJ^jUHn0i>N()ul{^2TE+A|TB3A(?4#V zwC!%5ksan~j&Eu$2qVKq1eULB9R&rTTn;j-Ma@$=Z5@tiTNJ(jHb;^?$l^X=+;o7$ zq0x~jGNa{Ri(Y@VpX2iZjQ*n0in7&x~?^`PNt4=6@VTaTR}i=r->M z2>1eb{=$PM9W0PLalzfo+2cQV^sfVVvUz-$V6EZyl$BKm&6>JO<(t|F;?c_1G-szFs4rAp>qM{ePV9KkfdR;r|%G zuPPKr?(X@5z1QOvO0YfH^L*}PdiXWkmUUupz=(ni=eD($@)`~|>{|!r)i?-8Jl*?d zm(nL<(IL&=C!#|XKKVa=DON#z*tlml!7850><$e|Z*7tXSwfXNO+z$sPefRLHlc~>X_U;&%+I)ALlZj{X+ol+;PxAx z&%lIae3JjN7Yp_+?VQ~bJvUW-C@v(qw?`K2EAn(YUxl6P6Q9BOxJ_|&MA@62Z_wd> zJ|h_E1NQHBALM9HOsm$DRBw`4v|NY^USb!QI{6-Gf8p9tiFZK_16m-Sh7M_phtEYjy9{wP($} z<{0CP2!JWlA~LQrjQEmSD(}9q$Df;g`^RSSb2BN*=CSHy=9*{?MhD9?LJsxwAu(=g zc%Fc{^lO}-7jaGyf3F&Uc?kR+YiWigP2h`pDIe}Z)qRQFC;F|tD?2V-TzTL%Atkk& zx7BLO!XfuGLMZ2VB97!$FTlP`an^nsQuiN$r?YMN3jE_ZhJcInebiYF6p(@~BU?4MiW5DF^yIqi0i?k&D6q5LRS0Lo zpT4i`boS~$?^Im#XSxF= zq{3|j#2Y#7dJM~LfVASFT8!Rx@k%%$??jeq*64Z6gY@@7gF3JVuB5Exe7?GJ#aXQ* zQQMyH+j<+uA97(>*L@t(Ae&EKU{7TtT ze>gfL^Vy>8{;zS6^b&sMahumvJTS5Cuk_TcO@cEFQcQ**oV<UHEo(bhY!%>#VZA;b-#r|}? z|6I6CW|oC3%;#T^D$PY8>hEGurm7n2UGBU6xmBzeBP;Ra(hIro+KEEH*P$YpS1@-T z8S&+FS9dLuvguBn5+wTeCFKO%WP$!&U^Kv0B1nFu5UODOmaN@U%eP!geku9}RH}+6 z^$9WPA}TNE?P=BjXn&~Faci|zSASUfDnX?II*_MloT~0@SWVy4c014MPyS0s9P0+j z$$8}{qPV1AyxU1*l1$R3rcPhv=J0Fd8JW_5rU`Da_Q7uMw?W_!1e9Gl{_TGan{rH$ zwVYz#G9AX~J`AvT$I|$+>Py)RR)|nx z5r!sI8REcGP3r3YBoc{*4!Xd4vHPc_wT~RH`&nsjxs)zV!2CC0odX&~ukzN${EGV# zt0y4O^w2vzZ@1j^43h1zx*QU9pgET=tIP`mqZNuO0Xq! zinA!VM*im@biVVYgylx!O#N#lt;S4M5+x*fzWb4ig~$n1&2AvjWKj-Isd7O=*NvvE zrlXbq*mJrjVl?U(XskuYQ5vR5f>U8{F)Ok$wkU^WLYjXkuoB=lAES7UvxMJBmgN7 zY=<)0t9aGf(273NXGylNXSL{MBb&`sgLn9J8&muxIii0G_Q@aRXbi84rn<(@%$t-rtxH+pin`I4o2A&7iA&D0@GH#BE0QpZ3cx?n=7+IPe_Fu# z$N2j`M$TG25y|9nlAzYXa}5k1>0pe|qr2sj@T;d^=Z0~VC?kn+#e@G0|Jm-0kT!$8 z?*iQwfVrX#sNyjFnjD??r$*rzFV|r3I-|(KE$1H$?(i6F(i)`v0gYkj)sec^L!yX> zbqC-e@`)$i*phJmxvW9YE(pYlY$8|#jaBhvg20D1?;Xg1+=qEK_%Nh$-kg0w@BW#) zOavJ&bI6&vW=k;Gqxy3{u1Te;7MtwS$Uf89vn>NdN&WjUZT z28}jxe(d7L_8qa8`~N$cSyG+R9u=&R0=AvYw9e zJR$B@k=^M={D)yG)!Sqwd`kkQy^C+4vyZI#v`Td&AKX^3Qy9_@dEVE(xKPBbD3qpC zQpPX|##b38eWCp-cyu#mRAHCh+LH477@$s)BclhSYx^v`F@)CvCCvdH#9A%jY^iYL z7R&(-5Sp%O%{?foKni9?1#6iiDXIr@Ba}_{Lo+c>DYBNJ`EEWT#<5T_ByAKj^wd<~zwMm}kZkIlu?Y9R&rac! zdP*$o6{=ZOjDf8&--10F4@rLT=rP1 zHW*sqIgRT|qtW||*HBJCriwe5PVvWY1o-@$C?d2nnNs{oNZ=sg@ZyjW45;&v64X<= zB#H1;w`d?QE3rAld1Da#b2>R!QK}^p(khovjhM*Me1&o9bqRmbz|MvoSkU3^zl-o> zyx=XKCq46Yl_3+e7SO&JwFsR80T^Xoy-#X&)iiH+s7374jOMDRcZ;%zx>_*|>MB3v zrM^qm5cHh&^FhSxm)5$01L<#eE4XDXJ(=XUlH}!{M5TEPvNZS5uR@!11$x#kmM$7J zv4zeI>3Go;hk35gU5ez#+yl>_RAst;LiO3+TzhvAcUV)KSP(MEQ$U?>LXtS+kH>OO z`Ej6{%tQ=)4dYLuE+rkWA9TcZwEw*8{_^N}4?+;cPNYk?C7NswdY;L2j5q%E7NdXLxgP5L|U zTxPBKhXu;B-`FolyaCHQB`ED3=~2u5`fJh?xUZt=^;I<45|$Ky-&f;5hIDAy=9k{S zg;P+*A=}41K*O-R0{%kys;2M8@a{0+7t2`w#3X%jpkkE158nHmi7N-H0|@`NZUs zbtOxW$#)D6?yQoA7do4?HB@?q@t2t?IC%sr1bV4&(&ElfQ6EM zFZ>hQ``l9pP>;zzz)g#9x}d|A*e1vh9RLi2RM_JhL@a`F4L-b()9WkxN}TT$yITfi z^S!{JBkzzdVCh5EJ@&&I2rWID@0W`Z_lxkQ=y9ih`82sheD9mu53fE;6~%^Ocb|{o zU;#MG_}f#$;nsZA$J-7tfCHnaix+`Q?9b3G;X~37W!ZX$9&9Z9zr;gm_4E;71^>4} zjAG1^KhZ!UD&QL()n`4bg108jN_?HDg9ka@XjnrnOJ!-Kcp8OeP9BCd}j|iVk+^r~cJK zq}Ig_CkM!~Ua3V@&$f{TBf1Ubx*SY8jy@PXjiu!&2D_%6jSDrStl@;t6mjLu@x{6a znjfJT+ar#sp@EsG+yAx)6>>75olHHT{G2*Ce~xJjVP3MR$rM6~M5mbvYBME{7`{j{ z@=+X>RZ)zEeOwsdWzxR1h|!4}9wzD~g=$4qA=pK{g2SF;mLC|~4@#tKFB?C0t(17l z!9Tc?AIxBfFeGX=<{T#4M)nPmcTC^!_y)1Mx`VW5$QYt-UrDuv6W$C#rm{#zGwv;b zAy{Iq0|88pb0FCbv&utP=~WL1afPF8*NQsF^lBq7t7<49PJs?j^r1Ao?gh9o?wt-v z*jXnwS+KwD!)DqfQg~@r_wyuFGOMjJGECKTUc?;z1&mE&Xq$3z;u5 z3#yt>K>8hJ>~gq(5aB>h6Y}>I!nhiy0M5glaw?8~wdwcbZ5^)QjUzN|p6^cYXex^> z0gGL=m;g)c2&eo}f`hofWZ({`vj2ps;PCuY5d!hIhWvhEoOh;@*}9TKJVk4ZO5W8~ zF-Xr3@KEN^{GFF109cwP(qp7>J*-8HceuHvCxqC-WIcw>v!ZyQut{jX!h4`iplyj> zN`FnjF9z#-S?9||tt16KVrmvE2Szw>#+7jPrUbDrGHH%vv-*$2Eho($CmFnDFOSmX?}m?t#R$1jl>#hioVV4(8GjQO#&Lu3*-sa& ziMI=tV;b>+Ce_47r1CmJVuv6L17|Rkj>Vo)Dkz?RP||zhw8?i>AUFQc^U1Zgq*C*L z``v!=NkM@aN6}S$WC_gaNKEzWRe~GN41Wcbd!AA>F%LD&RiK>0FC-3|!D%1_X3nE> zE8zdGUg41lsTO~2*^5~1!;b5eToqh`L{r=6ETk}X8d3^3K0lW@=@1+rZEQQ*6)EZT zlHC$4GIIQVN^S1--Q1hJTSYLxbmlGA=*T2I@#>oqde6#2lJTk=V)<*5jRi*TFT9#@ z3)h07Ab#`l^`wxd82hyeFytgU;6@WRRr2ubrmewZQ&<)1f1I$#5iK_4Fhs zY{t`+{}zFt%k#l{-dm&Zycq{Ei;U^;pd<)Nqy_=s+HTk7H#A|NT4q>8>Ig zE*k~s_q0C_Z@h0BCE()I{tm}BFx5tZ>p_M$DByih;}sVMxBMoLpUiQkuD)yV1Qr{G zx+9+FLOyt*+-LJ^3GryA1|tMLk*#WopiZM(fkMx=(|~B{nf>b#c5sz5i@`Ah%u14l z2Wn&~;sHMP^t!&Qz!Y(kT@Tto7H||<8lUzl42*~ij7&`);%*eD!j9hFCzXGWJB{q3c^umR=M+l+BT<#>Hg0$(v~{k%ouI(1 zePNXqjd!$k2%DVT2mN((LM(knfHD%6`K4~Q|M%>hQH?hd-dS7zlpt6x;?~;?E0kZ( zS~6c5sbYY+Wfdi%CURht zr1=V7u6i)Sy5%sFKH8F0CqX+g;cL@K6jz57DY3cv3&5cLqH`83rJ%kM*Zv**lk>ma zd`Z81?Q&lTZQ^skaZLM2pxj7O3hJJRyc0+EkIbXrH4epS7F?4Q@35<2Oo@8N;@cfj zu=AkuD>YU`V;1N)L7x#l(+YQj&B)`@*un-qRfiJve4YcyV7GD>o9(=`2Yi1(1%(~w z|11+0=v5mXE*{OqqYp=GUd#Nx@Au34dK!tgR3>u=${E5+fs*N)tc%RTAL)CaC9~2? zKQ(8jr@UZ&U8`x({6F1J-1$Eb(r717Ct#tEk$wmww&M#D)j38A^N&lgaIw;H?fTGp z0iLYn5&H6F4lr*$%Lqk8e_oDAK^r8pa_hSF#nAub1dM^)&Ub_Yc@>&U_U^LE=;x2F z^KDffT@NX*IJ{U8eHCB=(bif+NF{rrfL(+wjVARRzM*v&utS!Hz?&rw*mX}VzzV%X zuMUp-QGAJvFOd_gxe`M;i==7;L4yu zFml7IGC3Rj~L zLpOC<*4xb2@hrjTyR}_oiuLlvbJ*w@rCn;{3lLMdevYQj#u?L`IANDpe7-5Acbk!y z_JJbqcCN;PFx*3}g74V~jUpqU-Hv~xhK5cu?{;IV@P{a%;8YWo1q(o!N6)a*_V91 zU@`E*jC0dtwZ-4MUsI{Pw%?F+F>cF4MHf%@as8DU@orH&cBm9(FcS(IQ>g^5N!2a|$MXc|?g|ZXN?U0otZ%lm{rjIE}V@3b^-r)M+a8EZm z>Mi=B;Y;~hG)~*)@2s%9`~4k1+CItLU$IQsS-^NLrGFkBLfXVjnNQLyT#fXk=QJg) zAXmAkenyJStRjSiWlv*bp$;JssU+Ek9P*>b>@@K|jB~}A;c^F5X8GD1U_R{k-@JT| zRo4qAG5z^U((;Y{0<%MSoX}5aOmebFOiK8u()+C%5niupe`ku6DbYg$_>2`xFup_b zP2>U40xive#rI>z`|*<}#eJ9)z58flG=YOJoP`c65*{_Fms(}rb6d&%(gK?bnBX0$ zE>%pM+f5LC4Nx?Xe+w$Rz+bN)@-6$rdwZD#EZ7CR4kS4%6Iy&Y4=>WX`xq1c4!Ga3 zO|_IRIyV$8x%6!FC$8Q#)I`}LB}2B1M)R{Sd-u3xL2x-aSrDCDj{FjrnIF^U(kjV{k1V>PCsez28L zofoIi~I757M>%p&UdpN9=}{uJE5dR1nRsS3eOL0=P9n7Wc}%KSSV?0yb)T1 z_Qa(69gyiPL7&QhTJ$PV0%w6)@P}*SQjKgzP0l(r#22#qn860T@x!B<@|r3p z^CqD|048-(FMnEULxOaqS&uxpKNUD!{oL;V0QJkSDoFH6XxDu)IIuwI-bQejTaLO% z2Rgs>$(RvYv9168?khG2e=Vkb!bw)3)g%4S$BzXjzlJ$y3Vm!FglzrbNI%DNbV(=} zgGfmmNJ-0YsgZ@Y0*Hn^DAv+H0VQ6Q6=}bhwmIN!`WGQUplsoyG+mf4BK7--%+!mj z7;!c8_*Ot7hbg7n_LwR`@?W)whMGI)gK-15ccl3K#Fn_ZB22?ixWMs5RR%A6+u>Yu zmsO2lF%@=Xw3^PgQGx5RdX%Xd{jslcf8^M)eNcm{X+XhW*{X%0G3T9&VX=y^oXm6* zKCD{mmBhwluy0 z6@=Nd_gnPUHeaI>jq_ldONDIzdcq zzgq5n(m@N)I43mpEdf+&tbwGIS&@*-4J9-M==aE& zVAQ+=HwX%%x zq@u|}L)KU0$nWbcU8-`VD;}tZ^~$FZ;y{NTaR#gFv2UZFN6&6*S2E*=4hdLVzU}`D zQ}0V?DB4Fw0rlRFa{2!Cq*uClyG~EtqwRK?Y{yv6kn&X@Tz^NJ0q|W+IC%dzfgK312^)C&>Vnjn&)pN|p>Ig*cB}^{K-~hqXK;ASjBVl% zd%iDy1#tZfd>gqE6jmIk3SPcetuR7u2|421zsXIWMBcJ~?z^Tj4FeJy4qXrDQKR%N zA)o)!2jqB#A+(ScS23DoFg z5k;p=h!XoYdf3{#{Q=4>)!S;JOJ!M$`L|T= zSB|pv^O@k!kz6`@8Vs1++deXT@)nqJ_KfV3juAQYK)G)`=pOT z0A74IlZEq?u3G0$^u+t$#ydw~yW!DjXI8I!80RH^liI7@j8}!gBp}jMxk{ z>=Yt#3M@OET^(g-ZGrBPi^LxhOlHb5DGxdrzFK4uKsoQF348^l_SvUln>+Sl#sd6` zNR4m{E!ocxxaVu4=STYD*A_#g?vRhIF4!H$DKYY3HmpYPie(I?L{$srIe8_?eXyjc z!d;5VsbLQHq2HR(d#hCx|HNv|*P|z;H5wH>CDzRhm9jj9P&&@t`@Ey1BB! zN7G`Z1AoxV(>t`q;jWcpQ0S(mjuWoZl*56vAwkF@igrWR*yRmRmOG2Kay1`h>W-A5 z=E)kAJg&=A6p%nyT7E@W^|I&d8+Q?9axK-hYkC)@Ov1F%PSG#Z25xypYibH4;88>z zEB|{i!j0p2a#VzTuL#_sp!r?e-3(qTH_n$>`7xlQgG#~s&Uh}ke@8H?*;2xks6_XW z9=3J@23UEbTl!1foSzD%ykh;c8OcqQ7lUgmvz5?T+14WzPT-*kounQ8G8ldzB?Y%3Dg(m~mR zIXsc}@g1BYx}3&CIkKpzC{+Xf|HHVL36=tJr1w3%5(yO1v%u|zbJ(jEoQyI`<~Im# zf&#s87$d%$qe8e!9|dpMHWZJDCKo7Ctfbg8tPT%4xXAE_Q&AUZX+tib;N{UIC{C#Ay>v=*ss3!u}CtOQ8Ix3_)I zl_jdY2kz|1ZrAKobG+_vU76yq_#*R0k=SugiIe!UmC^lOYkSkb`3w+qZs}lAjJ#2$ z6?8&(L=Xh;x;%IC(9BxGBJc0tHfw6amVVN78-Lve0GJ9Ji5e(b>-hbHXnWG@3n7nd z0yaE5`eHE$sq~zBN*nf37lz@~0L1fT#BtZ-P}iN_NKtx8(5s5XbOl|Zl=~&`D4Ooh zUyO~vLYVyCgMSC$L|u@G_pi5j6v9gs^0u<_F?TKDj^T2crDGI1bav)K8Z3vz=s}^T&$RN&l1o(*ZvhAq2>LOu z!m`B<6$l_&0CnnVF(WZHnbrSz6~%tJ4J>IJoSGsp$2>}BCl8GT*KA;F6C@-(aiAf+ zl>#MdS}NizdD|A?o9Q0dF2pV6>NM4RdSxGoCJ7@0?+^=nk|;{}dv3Cqca3-?IY@9+nkZY_akX>qSWS40XPHi3{#p6-0NQS)wIX zhlr|YSC$WEvVBn_6?6VYt{BlRSWHRmlK8ayDxR`F+{`E;D*9$QB1mEPnD<+0XG?p! z2x)z97nDj<&;}+>x*K_6^oa&TNx-^6v( zh1bu9JiKDsU7Pq_`MBjjlD0_@O&bN)D4!6#&_!aeV3o<0c2_J+m&SQfhX?2+0X|n= zJXYq$Yg+w3e?maV(HAQR53D=(goYj%++Uff8MbryCpj_6&o053o^5y!L6d%d z(0Q>}afPaQVyh!qU0Nc^aptviPO5h{Mpm{N%mb zK?UFbo|>DKpXktbWq{A&~{P9OC7V(+D&FqQy&weyT1h927_S5**T*Z zubIx8f1aAmH^OO@QO%R;K!JcCJW-N{A568j3&qK;`6c$o&v$9{ysVI25e&Jp^(}8c zKOE-ZNZ$Otk07zlQ{Hdv3xiE)X`{r_F{v2{BpDba2jqSwP(}^P{Ys}UvC6c=rj9b! zNiDTnh+i0H)q_J!5@FMmjl$N_gI_F`L5dn=)uz)pcfXwahdji}F;zvoOPFh!`!U6T z)$`>1>37w0lud;e;ZQkx3GlwotbH2DeT2p1#r4IXVDjVIs#s}KRp0m}yFy|cb?Z2_ zg)-DTujh?I zjabDuFB;0JD$iL%2nEY7Db08Z{kGhr7WT|?N}^GxykZwi)FAHgbujSKyw1x`uu{5a z_*PmlpiV8JK3%|SBFJu=i!cg5%FAi;ikn7!<0&?me4;DKWw8EUR42wdR14+y&od)> zE(%!|gT5#w9(z~97>^TcL%QDXB)e+%b5CPfjL$_y{1n{S@6WkG$2x7EU+#4xx0h5; z8C=fPPP}4F=#ei7yIU8ytpd2ANK(p}F^E|U#q%EISQr#qXCaKFWi%M6WOji#Ya;rW zk@#y`a<)TJ+kHH4g13LuEe1Wu$kB11ewC?FiPgky-Y))8+CWc^Ku#e+ck2x@8e7MX zGZop!Sej83@cIL~EJJ$?TE=S!cYt4yDZ&#|bfY%6b!{%EqRxI}?k{3#|hy^ERTxixZ9xKEitJxwr z8bwXkr7pP7{U5{`KPxAs6iwsLqt>&ZZcK{$UhXj0&@1hWeX`>7ag=_Z*P0Ww)Md?X z(W)YAzS0Iwlz-YqFNnNvyU-SN39 zywl-D>&%T`e2*NTSwpEQoZ%m=l2>g$5OQSRNpfUT5>v7X)vc{hMK2; zq(9Kyvg-iAYMT$`a`5ApZta>E9AzEz*w`Pl8dVobEU}muusnS=bo_otpC6R@jM0YF zu@e)EC)t`d;3&pIGx-XIwsk4AgM`e??XNhF^z8}SLYDOjFP#fznmMfz;{ z-oEMqKdl1mz8DbmH`L0%F5Ad$G=xEi7vks3hXy3N%6)(7X6Cv+qmIfD#p7>T5>(4? zLPB$Uc74v6B48;gK3Q(-UAE|k#1wl!JofF)#=V0d*Nc8KzKTt|+MsfUC3f*oZ|%sCy9uByIOD}OJh_^^I$=n#t~zBt+o#A@K59Tb@U`A6w?x!x_|Xh&ci>KU60n3Hq#Bh^q?1lD0n}n ztfz?_0`S+%v~!;Q3Ba-z-!u0GuN3y{e=Ozpvar#?WC-CKl7f420Tghn19-nQcLHd-U%{Wat8#DNDacUm%iZLRw)&UW1 zI92k@b1zLybvIE|?D`kH+P4f0Fui$4ppE#O25jp%U6=|BCZx*OiP>~<1#a7P)Y!wn zv7<2k$4K2jlhNj37CtoZ9PHzzX;6HBW32Wq|^Ihrvd z?j+q;CW1uS6-bs1!Oz$wp5$&^Wh$#bNg18JKW~Bi7n#<8X_M#iKm{`B11^Ax91Rfp zlRs>#rumNPcP+9xD<-(9)t-CNuB4JO`NT=2m$9+Q0>?d3OKf}EA5^DF$?Rty3|>qX z9SpaC&As^BQ83*oUDH&RBxoyWWEH(;h}*%+v)rBPs`yj5C@xsyhY_$X6-40ETc!i} zV;`pJ;+-+QD#2Y&6=9GW6qASIX{a>^iII^<63i959& zyIBMprF?n=;(Yedc2Dv*c&MFPA0hzY6;UM%gbovivPi}=B48iLo=_|eB_t98KjSmB zU$r3~3D!v24s#9nA8wTH1vG=65Qg<=ydfV41$+F(POZIfZt}3E)Nun^4zy1moyY1e zCfPb4-LXf6A3z?ygtz?D8#I~SZ>QeYy+NJ>&ykr#K6(BK3}Z>&-g^pWb<{Rtu>bas zi9;e9#OEB2KkIiRrxQ>p07@9$q6I}p@7@KY%kqeE^uW78CB^pBGF+QinzBp`01Swv ztg62YK!z!S3pQ2&{xt>HuM%R(+g)!{+)gm3J)d4qgre&eZ1r>4fVkuvgHDkg8*mk#Zup%sF zyFadkKL?}jeZpCoRT;ArF`F(vbRBi$oy##DLFe8DFeU#srrY{|^uEgA0kEVmc0y@r z<8+d6lwCcSRs`vk5EN&0_&L$3l5nI<@KXwI_+y*UYT9ZU(i0@TO3%^sj@Px1#W7EE zAq+MQ3O%SWIoE!V5-%f_jqeJ<;C=dN?YA0c>oMX$LXD_w^uZ(K&4N1J-Mj6HzmG`| z^yu=ssZS+fk@xR2scN@a@V(_|lnbke18-+Adub;_>gQKM!!eJ;`P*jjWPcLp4m`xQ zqEehFQ9^ry(Kt5IQr70pVwjoG4vdB{v3#}jYB8-PDL^)Ky{b+9rNjMKCzyky}B<%HnjnNkw8lzVUOmKSQ9nUJlA=9b{9kD(kDJ+}UY+L2Ov*~SgFx{An= z4;52nRFjgTU>AV$b7D0?2QGidJd5ncvn616R~vNWR;`u_Rf8DIF_Dko(>WyY@v{ltQ@u{!Wv*VKLR9it)n!Zt`c2F0@eL2*v@&~lWo+xAc30wsL45A zAGjNU!_+fi*=?yQ*MN7(ONxvk;%f++5>myu2!)szLJJ*FwJm5IZSr0^lmeTZ$E85z;Q_zE!daXM`i4nj z`H}K=p<;2-#iR_tc?UtCBjfpNvBGd7_EiGDBA+4#zy(yjCi(-51JOoY^{y2B}Ly0{q*Ol9jbzA&ngX zN>{3HBk`FNArXoSeoSDTZOv=@^X{{UzRUg~{N~>E1#KYw)PLlh^4Lnx+ivAm#aQF? z&M}3LFSwD0_aKs+Ip2KMZh`g4>7qf%$WYuf!)#FueqiL9vNTZ~)bM!Zx}y#|q1U>v z8Oi%1YdrP)Vk%etVTGe=8*sQ>iUs=!+@byHbmCv>(3~MYK(Nba!^*pK980VZr@s&V z_B!PL+`No7Fw`mkXmsxkpUG>k&GP2djVb)pgC!pNkHPCG)VXhWW2o%hQ9&tO0P#h8G6+xRyi7>^va=z-BWA1?m&nt^@Q@ZHmX7Euu0yx zlh-CnkdR=2$qU~v+yr9uz&L_(-;!T_~+rMPG13-*5BYwqZ+F<4%Sq@_9(JN zNL`Ezy)&#pkhXac^13&)+rTZ&z$*}&u}rr7FOKzoOC)y&ef-Up0P9l^rNAG(IK^fa z+2r7dC=5VU3OdIvcQH~PBAr))fi2g2+4U=~LNuMUu4_U0_PF?l@?TnBjfk)f3N#S| zUhB9^k#$i;LW=2`%7R5c(;c`nh>vTk0t@@1y?l#*KLCUz*8$=Pc41AY9%HyrbFKz_ zAs9&Dl3DDkKJK_C627@p;p;&~`fy@V}Vy70#WE#HeAw4jYIy~&` z$TKlq6nS>1hXdNV)4<@-7yfN=lcQPA4>+zXbOO`u{S1V(|9Ij&)g;&+TTniz= zhreyl#kVMmR&_=OKW&u~#;`B4Ngv*Cf01LRa_NVI%tEqpB!AtJ^YJcT&Ds?eE$yQs zCP4-q{ErrZFB{c>BzR9;Op$mH^#0O?j;Y%IZC2K#yHeCj2OmB}g+t|YbloP}P1adw zsN)p-E~s^FQ?dZ!C&irHrp2mQ7Nir2#(X!oX2=5TUX0jYyP4nDlT;8Me%7&S@P73m z2Ef)Ak4Rty$ICh5*X^SA$x{N!=0D(Jb!X$2%rplzNMY)S)ELBmADeZKY31hV!uiR! zoqw|B4-~VY1t-xMjwUEGX0mAJT`s+sO3=o0 z6TYs=gH7eRG&%DP3GVC8gLXUnn8)s6oCe_x0!BOX7tXH>nIR>5_=~)Vg;tMSz~!(j zvgr^^%lyd}Sf#uhFZ|JuBK+p?fbj=&ihLIxHQZI+*U^ICvr+-nRDtdWKxuo)7|?0`&TD7H+p?28l3PVZ?0p54GK455n)` z2lC^bkms&O7Hm6|1o>(UI2Fk$kF1f%7qifX_Ppz>Ks*z&j7v%h8Oa?UYTDX|GCpT zT)`1|78oE2*K$@c*pB%B5*We$3v{UW`9*%z|8$+AGK2Wv@8u_bG2NCY=wq5Dq5d~7 zvj0IjAawn~a7<6^|4V-4!~6Q@bE*eg>LgoBI1a)c^QPLGv^ko=wl;&}Q(l$=Fh)x9Q zwVvZ{LcI+Mv|S5g^ed%q0=*EsE=Q{;1N~AqR{xq+E6DE#=BB}pj z5|f?hyvCCAv`r?l^*ua@ih}K_XwonCMfkk>VQ24-D8%(&>d?w1k6M_r-KqWIN=!wr zeLG9rL>K?EydH7P&@FGE!xc1WdAX>e7b-HVn(c~@pouyDb;-swEv@MBb!my18)=5_ z7T2W}ELwVc9TW@}b+%wk z9=21iuZr3nx{+bj%=L8kEe@Y-ITH;oprxZ1T$W$2u3vgHqkmHPu1%G$XAmPOPmMKg zFPD!S#wRPwl)oE94zlw@eYI|y`(M8PR%X^cPr%Vs4fD2^URi=^b6 z7{B@X{iH_?x{UvQ@86MVeOhiwxTeyOScs!Bd$gA*KIy;J+ywQpzqK;O;y&7fwU_!Tdw_mI(ngg z8FX|7D5s?p$+hc^g?FvYLIc0X=L36m+QKb2O;c5c?Hw77u+1g%p&;Ki){4Isq#GBc z`#XQ$7;&WO&_jbOBu;2tMfdI0pH3Q1tp1XhO3Up;a2p&~2-HzA4S%?UX?!)n&S`Cx zEKK6hf=KkU?jrcKH{b9`%~2!Rw_>M#Ks9RsKiPjiB4H^S(a={z!>^~;tdgaQq=f5i z50a#gY~3$Lp40EGnBOmbQF!{O#OXF*7M2Au@nU?+D(j?-jdm5G*6E*CTMhcb6+@s` zbE1vYy=^?mG^e1KQPA++oVgO_HBpfOfKBf{9SKpK^Q5s6|J}a%ZWyNr8WT+$wkH(plnd)qfwWHrUc zm0FRNh|&G#v*+l%sBK+|LhIN24GS9qdmp=z0 z^ArUp1dzkK--Q7|{*{TS8~z{dtR|;srscTHGtYcB(~Z5{kIG4eUt75a9hx%)4`YMT zf!8F5)kGUesgjgc!iEbTs_&ZMa<=cgbxq&Yj#QK|v?hdBv-gtnj3KZk`Wr)Z{7FI- z;l+<3DM4Na5?fcX0Vx0*TTLGSFl-$XO4IiZPaY)U;j#ttxiy2HW1I4-Qp>=`d(tVt z4Uw~FU4d5NGlViBlLf58I%K5g&opcSf~d0C2|DFJ^6Et2p&7KFnny=Stk{3gh@7_S5!!23^A1{y@eGOu z?+LDc!lBh^9i=1Q6U^8-Nh^Xu;1%FHN`O=}fJwxUE? z%K~u09Qw(X^MjBiMK)7}xp{I%-e~mUm)po|Ux8T{5j?^-+NhM^^SIQc@Xe>Md#znH zN-L3tEr^JLbFtpa1NHm74P;$&q_{*Lp*O8O`@Ak;tC8i7{Mq;SI=%dDX!$K;6N1=; z)9b$+MObw+IbqCmm~axsl;D*pdVXybFmWgL66m}X^;dtOcZ>RVZJK1Af0-F(1O~|3 z)HXEe*k3P^f3{f)Wb}x(6m1;T6U7IJ#{my)qwe*@_$XZH98LiMMrt;kli`VQ=Y>g9 z{x`&7EN?7aiqvjI4s7bo9Ui&Mj!(%<$-k&Y;UwxA=uyRSvd7+=3`*7mYpu&>HwTh> z2Q&zTu1&~|_;@()itFF@dg_*B`?+nNkm0*?A#yBfvz4Q{2Dx{> z!n2mz~lu5f+E;|O!I#>^1@Wf2gAE4z8g%ukI+n~@oX1+7T4dBuqCas zeJ|){#gODfCdBf!;-CXCcO_Nil^S?jELD`y?f^Q+Z4lTBipXNky%0eS0+QIDyBj>R< z$kyAdCK3I}mo8fH^V%2yYk1CbS@HJeM%k;7US75VSL$9XDUs2viCniUw^7MN$Kycv zB^PoFnybVthqKLq>dE^Lt@I?VZ$u^=`EjP=RdYutAIZ1MAN5KVraj&~>X<(lwp*`^ zkd#~~Ck4<-YR88Int$$%Cv%?T8wK>o!_icqi9uOobF#g}&h*Y!q1#v=Zl0w2Zedo@ zWN)9Dk*f-=p{AXn>ko_rlr!T4LAlJ`B-efycWiugLhKQ_?&R5Pf>sFAPj z33I=wh9BxQ)8I_R11}JwMF)>Ki*9#~M|BiYOpUZ{RB6DF&#~hpV}$R;B=1`l<2=$u z{VBoA5;Nq%O=`wM0j&0S1cN5X9DKKWXP9+-G%lCUK_H69OwBN{ zcmaK{bJFQrwCwPGiBR}Lz5t9bFBt68WYJ-LU-bnTFvz+{F0Dv@bTp~I*|kCNvd6)VbP<2TYmIBUNfF0a&_nR#4s3G` zN7-k>#ZLM;>1JEPjI2u^I`{20(E~SDv!1flU3LS94YX%Q5o1Rq(xsS6lWDdsDYT^z`CX?uLa=A<0=lb^#HTn6Rae6q;i-i)yWbv$GY^&l&3 zlIH!8{HfG<2bA&3H4NH?96F4>`w2g`8ogO;!|h|oDn2nXWo5D;v0fetcbYSX!=4HFf*n=) zHNqAK?^Nq|Jb)7<)l>(8aHEt?RMtj z9DmnQLdg_4BM0MG(_2@FS5vM2)SW-aUehgVn^S>rMaRIq<3CybIH~nN0>*G08>lE2 zGY(nbY#Tm@wD2WmO1k;5`Dcu;cV6UH>Bj`ue$XmYwE{Cuu0I5cse^RrbYOCIt_m9r zFR;0s1X>U&3Mflvh^o0^Eci(!z~^p zM!Qb*v?S;J`jU-M89XLRAnX6SIi;qL?Q&&TWzd}rs)5>7zw1zCOpc7G@I8eQE@Gk4 zCM}!vd4hQbPzlapHe>{2@<3vZnd%MNIY){TwD!n(oVPS_`;v#F%pN>)jOs{w;r&*u zm<=?o#DF=QfgFz`h4LE@`|<(TVTWF!wJ=?k1ve8i5AH~V{^P|Rx6o(Kh7L9y8`OMB?KX_Gx6lp0|vE+PIf`nW4}ktTKnOh6Tutm z)~^`yee3gAgLAq;`yUAjK2;gA)|X%8ONNJue+`>NH@Tq82(Igu__e4W4DPE|f01gw zrn8GN_^4h3sH?xAkp=nfQdZyoFi*St#F`c8=XqX1z)Psbg*jrAKhI810GjF#jYq(W z5PH-M3>-`y1PgE^+*9rEu1qk9u9NL)=fAL1nnFUOWw0mDbUR*$1NM5w;C`yKRxx$H zQqfRiF)1ED7KxBn;I*Ou{H-qPtvP!QTjahMGV60Q|B;H`?2(aV5vuSUTmJr77jLBS z@;C=Vesh7HGyfYG?7eh=JGs>!9RqnoWu=NWJy#$It#?@gE-UKIW6*b%*xc;BpYKM46l-$Pfzrs z+I(OQ);dJ*r4L3&n_VDJN_b4Yb5U_u$FIh4w_t;d)02-3yX^91nx&B~l$A09$j+?G z@?v#Wvj6DqTVIjivb?FQZvMjdz~2OdgXl4n_?H$x~9uk3b>0b zI*I3RpzkL=iyH;)ud+fCnMEg$C>GJ!MzTR+aELyAkj+tJEXYCy_w)v;1zk7b>m>}g z@dlIx0W9$$k%5F7Zos=#DZb0G%HH~7;KLRau29vt<_H7#Sb>bN(=$YxLO*U!FG9os z@r0czmDCBd8;u)zv51AcXS-%66j~>OM%5j;R&QiG)X+S7J6+f85-TnAndZPRJ zuDD?fAzmc56FV~pXCGu)++ubOcB1~pN&Gza*6>aL&Y>>+JPQ!)l4z0pj7@7H1(UZA zMyg(VkjT%E30!>Km$e}ouH1&lbFD6>6~NYvir_XZ=)!lktc;X$@}pn$&&J2_cNBka zX8NqZKJ!EpJ?Fr`1LkYQ+S|cv5zIgN|2SLIs}yU+AVf+ zl|Nc^#FU=J0nOdij5Y4wKxLc&mK*8 z*B+KIS({#k=)H?bDIa4dDYiZgjOv*p9^AzZ@v?;0mzZ-miH?}v)YzFg&PJVYPT0>e z`wd%WP-FRc7%1mFqJ{n1RI+Hn;JVd=A}i`A&K+BI#0eu5s;qo8?%+zoh8Sv+u*^$J=% z|7-n6O2ZlLhA?wqgUvw2D2tvQ(ip-HXb6JulRc5H%xDnUaM%%pfvE4r6DN0w9G#1|SXfSceW6n;G;qYfrUjB9TT&H{9rFhINf8 zXEV_UMlgYVTf|h{0ZPM3un2e}6Nm@&=*czq0rIwcOsBu#QY|aze@`mm;ZR!ZtRZ(} zKn;%aT%@w5Yf|;`eY=rlxy^~?RbS13v$6Lj&oe`X>*pd1*Ue1u-Uxdmemb)b3!gDx zL5o%`kZqyh_7jzzGopGdkRtXo3n68?5I0}fkeo!(71aY2&WhHYfH~$!X2a$EEit1Z z)jpG<(`$`4_SKZ@4$dyU#%Xcev+{HNU>CcV*5rNygpAIp>wskB#6*;idcar(URi6Y zzNOJho#a{>;e}QktnxJj=<|ugV)9RJe^)Gsu3WFNbyv3lYvtXm6 zJPGCv$)mcqCunwExQuQdj?Ctq-jUl(EwBUKDW0ED5$wylkEyB2GkdcKmX9RA^unp^ z)nYa%I-q|mVt|jWvZT)-hlFL`X+tniHL$b3+Rr^4erEqb(ND3ag=I}`!X`)!c_-(p z*-*l&~f0bIjiMZ975ne`-4?BoP6uTLBuKFpj&`*ygJOo5GvzIIjgTkaXY zr@_HR^{$P8M>K9FSJvF=7LOa7q^XG5i;e+dtUUE0=OXXd3!Ui5NxL$$hPW9{K>6`6 z#mMyJS$^ts4wjXmvU@}AhCXZY*2I)vG?U_r>PT9K|H}>{#&@ z@H>)>G>CO?(T@cWPA1o!_S0x3w6P-2tWGgp7@vF-tGqSX7D}Tg&uSo+&t~CtC<2N( zn;dH&h@YVv(Wn;3@Cx(o5)`Nwd1-|W=fcfDna>p08ekU$X+Cx6Q4PE|f4#(ovT|xR z^)4aiWbq)KBjM2={hWZS2LQ7#Ya;s_}$b@x8v4~?@=UKK6%jPns&GvoVO)V86 zJ_Ud=JaC=Avf!Z1nhZ>%GUm)<3d-9tG9+4vJd^|k&X~6mvLPO9rWw^_1Zfx-b1;HG zPk{}BKn!MDNE{aj)9Ti)F4-n#Sl^-oLD{|+;twWT2pstv4YbMkj2&!@V3aymEczgl zMx>KGO2|Rsz+^Uw-S;6(v?p_;BiEKFJKuEcNtNq$g|Cb!l>=29S$F-!dV{M7TW3AX zJF=S-dsAcc&w14Q*Zh90OCoVsax7IRQjzY*Tm;Cw`#$nW48(14f+rnev4Y0)?Cu}l zyf;-USiZ0f0$qPepXOy#D^gesq#l9~1Rg{@_bd-tQmypv?np+b4}Myk^0fN$6hsBD z{8~dh*u&c$X0R+uWD9^QI8tq?gK?kq6{tHI%8}~Ga;depYzz<0b<~}1pfb;D%tVK0 z)5Bl+Wm8a$i$^dq+E8`@vR|X7g=sPTLpZ4=a@&E;uk=%=Lm%3vfSx$$ZH+OnUL%e$Z-CM^YU z2`8E{vYSlm^x$2BXst&kEA%N$i^bBI&lrN?DBWo~17t=Uzz>hEr1rPA4n)E?>#~_e zUac^>eXzv@Y$I{p?dNtK zI5Vlo-V2h4Kmt7E)gjT8S8VO2;I`K^tBG~*pXsynXzhlBY_G|!JDNPHn6K&L1dJKh zFGnr;la2uA_%$!_mC_Aevl7lnXt^^5jpQu5GHBZavxZ&$2;83Wf#ynILF>r0be|Hh z_j9vLJxe-gZ_VB>1k}-iK^5mX-#L-k8aX;f@(D=T&+j2D0Dgs(=tt#niDM_yN-Hr* z9z17vwRfmGPfAlmV4Lqy7vaOU!z%;z&G+*Jl`9p<%oqC`&Xi#6n5E%B5Tu4?>(O7H zhyK2y@V^#INvNmI1CBiPh_koDCMEpW7BkWTG8C=kle34PTR=~iWJz-AQ4HV5xLZAX zFxOU*QH4mv&uw^VZ-BRL#aG>9Y}M9FPE9Juoy$HRMk|!?*x%VfR^}6*jCdD{8ShYB z8HhvLjkPcY+yh*!6d*#(rMd&gj6ZXeH1&~O>75AgQ={%4tWtvc2f|sXesCu~)2y(t z7+<*62Z5$bW0Va(Pgk7>I3v_CbxzOu`t&}0qw@NYn2SDQJjv0Co>AO5c#nw#h zXsArF?hCTb{nln+JZkTVYb4;7E?3UyefN1H)%iG{1K1H(bW(^BRw?FW~C;gADUk##XIr`Oe{d@_(85{6E6Vbkm)nT zI(}@EIg?YB+i0>Q7rw#L4ef!}T;{sqHiLYz-jNyX&?C4^x~hLvV?0R3Pilu$XAmB$=k1+8L zh1#KvXoWWVs3#ZZj4fZp;}hyBv2jQbvwY7eJ8@Tb6@v}ykQ;qJjOzV3YteW&Zh3F* z4KG1QLD9@MJDT0=?BhX;US{%E!i6c=W*l^#`qvT1)dR(&g&6Hq8rf`^#WpRwd@~r) z&bS8egL>`irZL|mjo5mcu`eet4X1`Kw7J~r7O=$~NEXqOG$tZ)7#tKIzicMKP#>T0f>1`ZWhWHdMo*O!y$n?*lGgO_pS*wEP+?GQL6g$VxV(Y2R z(Y&GBD)Q`trt!#8+AqfT*)v(zO8k@RHMq4q`QMaXr~s@Z{cX==JlZuB@?B1v_^yMn za8gBx%*hl(Bl(Yc#dl&;VIY7h`F^oR(6_mos>k*VZcqHCh&Jo(agqRad%q24?8}Fj zLAmnCHn!F{ff8({k9@g$#XyQBvHAe&HP25Isi89hwN)m@*nC(9I9;%dkiB694)^3r zEgU2zMSEcfHQRr&@PsuAmd6O4&S!FYQI2HtYJWk;{mU1n^iw$Yd*%r5(f#tZILT|* zWgKrFO)`q6V!hPZn{Bete)^5+WLWBay(#&420_6;glRVtTJxWWfjCM8eejCm16!SB zBR>?beZ=26l4SLuVM_af_%R;YZ0DzR)q_A3p>_R|)6kaZQ=DvQMq;3$G?+9Q>l`|d zWWdGdW71~Lsy9O4Sc@7~tN0CF?zZUItE+*bj4PWcK1=!;eXBr3Yu+ycnXc?dl|8x@G;^*d(Ygj^u@U> zta$rl#T=6&N+mf1YF1$*MP(HO7d+|l`!`38+P#PlTv;N1EeFg}Hr*2LM!w~V8UR^B z#T<?Sj~`mvP@E^FQWHsLrR#nh7KEe^~^Yk%BVE}C@8G9TtV`Xa}; z+zRg@m_obRpM9OOY4kW2*x?@u$HgZEItGgL`A?}OK+IQdf;Aq`F1ihl=Skv_)c}FJ zGB&3LYc4HZLC=sX6rfL5JcUhZwz zg&CHOD-*#4P(5Bpp;DDu4&Nd34`A%pN$k};N?AuwE@3$~zW1?bEBWsf@@8AEqt^ zx9;TSK0JoEQm?NRuU2H#q(ww9$)U5AB6XV&7fDCQpeKisyXX==XZo*rk)N`{v2fuA z&i@K4!9M-ch9GW(PS5kJ9=oYtUk|NDa1e|*M!KaGT`Yc7O?Sd0qubPx9~F^PGf8zc zXeJsQ(kYKt0XZK51s)d5m;S;yL>y496f7Z(p|ZQ~t;RlGguIi3*EJ$lq|kZ5i{zH^ zoejpk&P|2SIvD~#zs5We1e07r>+T#60Za;_N6yJ!Vb60Akzq@!W3{E@epo5?Qe^p2 z@(gZ&I0$B~$%!#`ia? zHHsyQR5gYn)T|5ho#XLvh|8o7NdVF*CbaHw)yG=j=`2Ckl|nZ3rcjXc0$^@+p==m+ z4&=Og8?QJs5Qz1$taOA%mjEvC$7`=fYS(iE>`NWkHkShZv3f-!4oohcj*if}1VLL1 zoLnrva_RXJ0p=`0REh3mE=+!3r^O+s(Uhv1)B$ZVz){IqJtbZL1VDLH(v5e9$WiW% z#irXnIot}<>ZgyzKh0a#^zj4!8@8_`aOUV!7h=WLuy)CzQv4oyrbF$bDZ<9w=#tXF zgIvpBo{a`a8uV#0=_ou1#7-q|yXrDEMsT&|&b>h-ZDd6u{Z(9F^J_v$Iu(3d1N-;R zEo*9MTuN}S?`D{=5J)met(@udXEK3;PXn}ZAG}?Y4@<$STm$>2F3%l>R8g_^8U)*X z%QQnO<>PFO?Zh?F1jMp$1UwF58t~m`Ols4-jRM3>+DTE3#uk=i@;BXSKO32bI8mcratNhp(W=KTiB7CZhWG2&K z1NZr$V5KSV{7VdZHs@p}?g7Y^3#>BbZwCR7@5*UejkIadQj*^>3U0ECWzxs2cM8(q zhF*eL{|jY}N~aqR%ijNkWmSiskb32WQ~x^O+#J+71;OLQ!4pILlqcrz=NsM9Sa>_K zdWC(?w`uoV;@ZkG7`p7~nQxb6KK5T_R1Z$f3}tD%bRB?`;kbpVWn~MKWs$qU)s;Dy zx>K3#a4dt;6&tu$KVfWc1ffo}i1e2rQE>gv-CF*@loq*N#w^t#55_7G5j(gtx%;rs zLys~=+&Ygm6fg~#&f0l)id2rv)N_@{p9rJ8%s_oOoZ}?Er|6UUQECci=0o%2`Zpgv#R^rR>CYO?4_{BvOrFV+^DYenKVhYp&d)k|jJVi%xx zTYP}8pH#nzZMPc=rvd2?^2bX2A@~@7)#5>gx1EoS^h01b{1kr<*FbYrv^Z2MU1zLF zYQui%{gs%JRo@iAZM5*tM3NYdDzdjtrr72Va%dHjyqh~qwAOn1HC=rSxg`1RxS!%4Er7z3CIneZj_cQMpqA%-em za@VkA(<95Z0xkiTR#b@F+gGpmcLQWuKRmzsU6)n(5V28|>$#=UJ{TjFm6ZWCG|cN* z=a-g5Jio@rpB+?o&{?oEfU(!sLw@gdUoi+T8{E_eNg z;;A?EUheURhljBt5hX0b?aq3BH>=JZ1PEjgo$biE4 zQ4ilg>k(eU(9bRqE|SgQ#Ytw;>Hbl!B|D9djxN#R{ZwVYA&zTdJtKF=_#ax%23+yF zzwFwG5vp*WV3r#Q6gJ8>QSb^t{s-i~7zKsQ0kaE<+!QYH&I`NyhYV~RF4KL`gBPyF z(GNME|D>q5#Wt?4u1(F&*{&z6w18h!l|ZJwWyqgLlI3*2>~HLe&XL;Kj8-(m%`ny{!=5o)cos zkh@VSEU#eT(L{B1Gl%rTzRmB9XNX8khfnQr|Eh;ReWdwatSXj2`cL_`ym1xD$H#}A zlN0o)0vSH1dxcr+yHmN4#M3Q_-}@X2Yx0lIsCtj`_5-Yoiwl*Hik4QX%Z{z|R2qU(fI%E3I3NH%>T4lh}Xmg_9ZkpZn**POZV8?GA_D?{xQ(0CeIMm z^MHK9S9S#%2RY37Cli433`@Uw)88*^5XbxWA=am$siI2}GYF*<|2~R4VHdM}F)Xew z_okjT)`CGy@bpiDr362K>Xw{+OhAVg2aHz{9(amLY>^rvCpSM>Jw~ zl57^KN!Rb@`{#s0?BB^m2G-4;XhVx59*6zi82*%@jWTet=W?N+;r9vXPj~lC1OovJ zU=8Kvy^FE1Ci9 zr|)Xya4l>u-%+O^{s#f;=wC@lKT&HGg{5y!nmV%-dDf3IsX(f5?lL0PXT>}6Z2-lZH*?$3Kd5F!5M-I3pu0t&>2jOPRtuwHEO zy`&kjQSj3KP3w~+u=}(MYh0p)oU)MOWXf|U*pA}(~S#%z(eJy~00zgFf_}f2V80;L7FQ6wt6B5agz-4GEoC?ZUVpHuHa3;GeDU z1Rx|R@SGG*&>C%7%NB+GM9rS)*xmR{HrbT&Pyfc^S1R@k|NDHFpiruGCs8()_lacI z&7R}Q>@Z_T^1rt&18e@{R+?PaOTDU`V-V;u^Zh0Bh^7baAJ;JcrTV`cs0qz+X*)5! z!|0ewgJZG&o#_%&@R>R#4MAw&-*Q(0p%03e*E>stOeK`~nIwvaP6*{*YWxH#7_hnj zL%-=q`ZjIT$W67k5{asjgEuda^L*Sx1&#J7dI?`x|7%hB-lCEB47t4X6%-fX z1;G?B27+vO1@?qQA2~2-9>l-L)bqSuH~sfE5T|sWBsB8gBZQEyQ#^nFDoE~jb=NR_ z^8%BeS}g@wL6?Vug2MQ043J=D5oc!+=laOP$-%)T4&bt$w|s=8K}m_piB+Ewr*;$wW z#edTe{WR}eRkKFT8gtGuL*!+}5nyp)-@SW>An{d1@!h)*An@M{(4WAc`hla)@7@u= zlMoS7a@9F$L-J6bO+J4~K_u8eh+r|0Y5R>zPh*>#jS$&4&-AH$0!g2|6@l%0n>kdU z1idtr`Q-c1YfNeuN#*4RPU+`5BAis-#4`bzC^0Iexbu>o3sl z3^1c^e9^t4ZCvANoSSNpYm+Nu{DGS5X?$tDZmwqT@zj)WoJh}#e}BsQP3}=P@7(yh zEww%FkSC_y8?e4rMT5B>55x4HvhzdIJ2X8A=^y{+7qt(c4xVfeZ3SNL5)aScgx_9t zuV3_Pxn6!#uU$wst0H&`NUbmCxaziJ{D-Ii{oVmKpZx=0sKaHk^?3(RyV)Btje(Y* zHjlc8?zEl((fve!8Xo)trZs5CX~-8cPokXj%NV!D7*x>bqE5?0cUDgU7R7-5W0pTo zp2>%o^nxUQ*Zk2Y$JVrbI9`h69I5Dg-L=Ofv(PJ z!wW}zMfOj2(L&*r#?k<+>-M*0hD6`tG!F_)-wE`$;Si1gXB_<3OkZ^kF7~fqfcThp z0lYEsx875CE1VpghROd4JO5tY1@irm*Bjl#W!!ivd=gs{?dyz#r)!0PRi0r)QlL1?j{f{l6&v_aoq6h(G)2bIY6Ars-3Opn&*gupV@; z>0w*2-^TuFVS-i==lQ*@^L}ISXdn@~*2DuIzT4A>5C35M&JWqB_a6z=B-ZbosQh7o z>p5s0=r=YV`2T<@J&3*!?Q*7THl#Z*x{gzEeGR*4|7f~kDM9iMGljI=s{ypj3onWF zu&uR_DARugQc`EfRB}e{dGs4BH;FAC!F@wt!}dQ9!$fi6;<&X$4On{)(mobQOV521@J65LMs^fSZsd&r&pM6EtY zGiKRuNREVwkB^h>Zaf-fy2Y55goS}ldz-ThuSz>;Y*4sb6CYJ-e&2)-^J|<&oOk~bPI_NA1P)rwuTSg7 z@8F!HCXsFw7}|{nQMFuFow9;nuL}fv4WmMaBlpBXGsm|5N2l-cOs>S>S&>0vXf*3# z|EK@`pLIy<)xz)sIG*9+qdDSV7lhYg6eO=lZM~*LL$Gd9n6KI|BW(Q%61LaX2ZovD z+y4y1&I7csQ}h*?6}$0N?mHpU*molIT1T-tq_8fNrWfXty&hf=ZUNLxK6)~>F)swRYnb}dE9YMdRo zs`0XbEwbw%q&RF`S4i=65BMPdHkNpN@{ws~4vaz7Ke^A&MOi(9_X~SiAd&o~lI&O< zZ$hBofq>V0Nd{#ea!Q)HweGtwnhaq@a!JwiLFwP(6w*&`J2Rw}^JctX`s=?8THlMruc`DyR{0?~2 z+t+lbZWton_)ss~B}vmMJ|Sk^9R^h$H}N;11i}F4VkF=RJqe%g1ftGd0u_{W`~veF z6jaE)$n?*$A{HFJcn?^jN$^KcNN4aj*o5r&|-4O@} z9Bf#wy!EKl+{Qh!mAJp4Or8gx3My}e}OLRk}K*D8l*^6OLx}R)^^wbGO8R}`L$@j|(`REV+2FmpQ zt#AjlTGdlU$Xk5j7b7L*fkJrnwZhwo~SR^|NXaLTd510i+ z)yRc4qTET=wRH`y-^5gR*Y%lRU3eMgQ?%lTQA~;!hvE3eaUy2=Ni`rkv9NooOgZzw z<-ps9tzMw4Jbb9HtdLQTYHDHams7-|!Nppe`{k4r<7U+BK~$y1>}TmDruRI~C!J74 z9?>^^_kXKK1c`dDQTkof3*Ymc!KGm9?fQfF{;pjbTgrb6??eyj#szm+lG$b^|zPx?VZ8!m(GWUiAL!^ zU1!+z5W1LvHNR_ZHVqg2v?uiXXpVbS;Ku1`PA+=w>P>}tOUVRCDTLGhFor`+)AIE8 z0?PXWDv8gXea-Ed{`jA$E0PZZVBiVs(aYJxhsKJe;*F`d(EYPASzd!xXYsM9m-=R2 zL)@z@M8?ZtUNjcXq9_jTnT}5=T+utO6%C&DB|-s&KT#hs|IVm@=sWLem!)7WuMq}r zFMr<5)SjBYpRNhHjqZ2f@CDrG&1D04o2RijvMwzotFsn|Qj!{AQ}z;l@js{!;MLTR za+v&QD2ypW^!rE8i?7pc!*#l%f(lK_)dOW3#(oSKrtq#sp58TRmeD=r`=y9Un4hqrJ8G*2|N~F-j zOjkzJW_niMZdv7vbOGeHQCWO36SEUTf`<)eIWXnG`3%l^Wd&IrI|ZILw>+j@g-VS;3%{29Y;f_P*u$0wwU0{G_IwnTwHAzTk4 z2k;}HRpGZ5HiupHoB{R=3J^;!c3hdnHt?m_KnG@a9%V_gnwpPyX*yLw^Sz2R1XdG5F6& zkp2=H-aKrG_Z8+nbDG03olI_o+EJ^FSc2AhVQMXV@0AiK z1Rj^G+}ulER7-)GxLf0E9av)y5o!`xe??ImofF^~%E~}C1=s;4gXf$x-1x2z@#(d@ zr05h!*DPGN^zYyc>TjknA`w0bd10O0xqvdUkf`J%sqoTEk#u!vfG+bD>(Y_qS4R_6 znxU#e`&A*T-V=9mqk|jkbd@Lm%)?I$x{?e(+R!kjO){E7;R+7J?d}JYrV;gh8$|52 z160+yEW1hRpE}fYG}jN&bDzcQCq3+#o_8j>Nx#$Kl>v6%JjH5OcLXA=BoA)2$Cm~h z2`tL76nnZ#2}iiR(%3m z9kr?C*)2&dDg>~`S6jqU8Lq74Io&n#uX01c(LrM+?Eb3}-YxosQ~;+kn@t9@-jsTk z2O4fU9mv3~0d2W`Hr$8vVl2>*N+r`-0zvYzL2rIK;J)*tk?vKX6Ut{B8^KYk$13B3 zmr(kQqxOja-f!&~YV}3$Sv~SC|K^Np>g6kU6$+Z2x`5MzlI!eMuVh2EOLDi2X0#T> zeFb+8OnJ}&j#w6T&YX?drh4BkCf!c*<#-L-auPJK`LZ+Ij3k4NKY*86gwVGC_DS<5 zi~5FaDn)JvwoN6%MvAX%J$*5~u{KQjg#+@3HD6nxTM0HibP zntG-7Yz(_sZBH`5Prk;B7$jNEW6zF5kLZfYMBDWR>tFNLK(%C?=CuhLj(k&&0Q)qH z74R}h^UjRDgLO_K2?gQpc|D{qaf+wb^rz<;@ap7nZx zaD9%=K?&IQQShEITZI>EMj6Ho16S;x!%_TOeaMchP6w+r-tVhBsABe8NlEMQssCO8 zPx_QDxIZmAk%)dese)C2P_X~e+H4SNTAArZO5sj%F@+{vYHe%`Se{Bma92trTHa4u zbBvMBGz>`h;&Y#KB-|J3&xpQzeFxmc$VuArBv}lqkKpq*^uwrHdM00(BZ$BRg2AT5-fm}_% zIV|Qq9!x?^eK}307h1J}VFp46>|8qU{U-Z_cMsRDAME)sleMD)T3zcB?5@?qv6e20 zhfQfZr~9*_=l%AC(JH?y;Bb}4CEPjVvGcnIuPk_GFp)4S1cC$Jt>$V2X>LY>1z+E3 zM1jq(mF6gLasRVfNx-q)^ru~|3Vd9A!Idb{#{Os}T}^k8a) z0=+d%W)lfc@CNna(@%Qk?gRpbv&T7!&OaFJGwz8Q99$dg7LigADAZNJ+G-xYUdn!H zBX)!2i!$?GT7Nm^!|=||fBY<+p7cH4`(BAi$I`a@mCV3WzE*C>G?!he!HBT@bNuQ1 z%hy>;Gl5DX0>U+vGwbCVkc5%079PWr{RV4~*{})?%Sl*yM~Gj}wW^&KJdee4$YHGY zLv-Vfc_VOXR4DaasxJorsDC&}1M(tVG_Rh->f8A^qQUtz{}Wu3t(D0B zM_L8e9BddAL2uW0=ah_HqN3hO>RDB-wOhCIGpC`Sk19QWOUvQO#cC6Ju)4y7Fxfj! zEak^1B5iR>WgfkYwP#)yF$hzVJ-m^rBI|Qbix6E>9rkozr^HT)n9qz?Nqvb6QmI_W zpI$4DF7iaoz`pA@8GVIm^LjN7B%SF%qfR*$Wu|@=|lIEk!a1U&{kq8feaG!P$+*D+K^kA?O zp73B{>+6>IGUv@X&F(C41$hD_k)bG0YiR60p-Sh!@WhYeF`dQDNZPHeV8tD`(wD68 zBpt)(^3Sk(?6YuBQ#l*+EA_guz$3h^u=Wl2Bz#GjOzh!4$1*r&4PMlhqgV=yJ0ZQj z0jT-U7Cc^KJJdEh3{5n%YSt*PwqrO=4;&!gWgEXRI8vW~3&7AkmKO^Gruj};LYpHT zhw~JeZrjiT3n{b6BM5%83yeL}Tk(D;DlP=@sTA)QWldb&{;;FjVY4>tdZ0ZQJDpL& zTyBTNtJ==AUBn^Yp(royFV>`0pbHUM!<9DJ=%qYyZRM#;aw=!@KNDv+7%u@$+UI=x z*x(f4?}JsZzubNy*u_o4XdKQjxZkEZk%}pf59w45#rIZ(zuUgit0Ez4AARn!(WQCJ zY?z&07P{UcH7L9mITfkB&F*@XljN9%zM9BOjuaDQlrjNdVcj{;uj?a`zR@vt69;)f zK3VbLWZM=$C1OZnoH?78;FjuqLM~K0-o6421N37&?@ADNKV;Ac=n%Qd%7{JW9Vt?+ zANGKAW;x=0DSvV1FE>K{2yJVANab`?>(kV58BE<&2(VO7F?0+<3fxj@4Shj0oL45W zV<%;C3r;`@g1I$iQR6!#^r_WU!-MpvIm3Cl$NK%+%PgCd#YU)AP?j_P$4id*i8E-= z*h1_sey0bL>ytL97Ts#~MtCTCcG+)FAh9Xg43eQmf1hG8 zCEDeTH?AJ@yuzj&6o-P`S;OA8ps!lsa7Vcy>SIzM^|%_h=|`B)#<(Vqmqc3l!HRCr|i~kZB)vN z4a7<8fIJY%xbXV=wg-O3JMVpb;sO14kP+3d_UUk&eUS!R_%vLR$$*5?v)XZ&Hzx>& z$E2#;KzS=VR zTDWgngU!w(?&|wSj5`RociXa zLmaWu;>nG+y0T7Xzohfw9DeCoyhZC+NI8fVoTd@>Z1hMJC0f>FxH@sL^KT`;m*|Mp zdYhu(a4^Q(f(CwGUU)$+ql!d@MdPuZl1qIg5_!9XlSefjD?Yuad~rh0Skx=e#PX^> z!K;-$mi<{WukUYkuSU!6UBL&En)#8XH{%7XQA6?M{@uWUOVSV;C^ioVT(SyijZr)P zgthp6)VUkhN}Db<6$15?vwt8GGjE5wHWoiX>53oL(E0iUL#j?O7RcAi>*PUm!8MwY z7Oy>!+rDy262A7##!WuGAW|c7;31-~D7pbpyV{QOrpbv_=JO6li0nXIjnw2;RaC3o zEy1D4+u?`MinJh#9T*jh5N&&SevL0SIih4R-Z;w|wpu<<13VKp(tg_HuU}v;sa_=H zb5=g{mVDxa(2|!q_UE1DS_}CA6J5dYH#g_3agUYd@oN8N1T8I!pJ?iitoy)QU*MKs zYTP{^J3cHS>uhNTV0y0xH|H%^w(fp@d}<_YTc35P58~u0z@MG=xy3T8reu{x%)X-< z&ki`_V$$-DJ>J(p995>S`l7ZzcW{WX@EtG2d#m6ZpX+Op3h&$F4~#F3_d|L?Z7?agi_XD1@Bdib#_ zh?eBuPdOtk@xtzU4Xiz}l$u?P2vgw6STo`wbwDR!)cygj`K^@9(@BxP!-x0X6e%3N znzOe<6fHOItg-K>fU+;t6K!8`cnKJ1!(F{XaqS<~Oo)ngfJ88lvgg9{clDWvNi#3O zgDR+jtM&sYd9TE+!c+;Fp!_^FS7C|b@z!Y{>$}=qw4+AIlF`cjrWZ5o^ZKQ_-tUCW z=mL*8Lu;y0<^5-c>Xx2iDqIBXEtp@2Fo(+8(=5dQPE%954sQbyHGCzL zRf3{iwl;j&Ywv>Dc_3@B7G1tZohK%DrC>s`x9(6MI1;%f$~M{AxA!uQyn_!-u`(TI?ZNV#-+l@up!Gz$0}>_ z7dw+Mg{N7zNMftHhJtj5lf_my5Mg~=1>azPtEX^lEiGYAMkR@GQ{qNbOve)({V(`H!)B6Cj3Y= ze$3I8JDg;>+y&_jux?2;Bw$qzx)hC{$&GpoI)wast4CH&G-+>S-=a-t)hqd_^|~`? zQ_W?4Uz8Mt5_PnsLD$GlA2}UXN9k5I7jF}p-c(pQ?Cz4?L@Qx9E3GBwkRzLC+CGyY zp6N}4bzx|R=U6{EFqU$Sw!W}I@Mb>th;_Sgm{Id-0YA<@Dbo7W9ZNFpfqzEob;Nb5 z0Gldaec#Ssr3e$5Xb*=3rf&js9m5VhsCP(wi`kXxMF26-0Bf5LbkK=^>rAgh^Bh81 z!^llwYvf$TV(tg4Uum*qza*AOs2h&648Q2QDb~tVjb+w1Mb90R{!}lP5Qj z(fyFp=+!y5WMIFcCLBUghEcoVbia*>?;#Pcu*R0^+;2kt_ROvK3=8bxVz$L#A7zw* zQJ68~h=U5r*){j)?JqQSUCE`DMRhQ`n`v@cNkrB0ST;PhZjg|?Ve_fRcnk;=35-Tw z6O;~U9SLY4FPyN3(p~YPkGhli2#ux1B1KCt$oe5{W-=?hV1n)SSC32=johSI~Y1?T+{U;>3%y4(FtX1oES@%mn@<)>IIS)ALbp`B+2fuQSWob<9ehiKIS>m3Xh?ZU+B-@Q$AT;S?1+P-QU#I*9eI(%qk+G)=7VUk1}|^m(ruGJ_ij17(U0q-_@o(b43^a5Q_(bzGmEo z@G7

      tp}51kCXwjX)T3&QGcNgbl`j_!?`!P(ber4A6|D^Oz_KsJc;S-~*c_31h$I>cvCt!!MaOuc~u1 z9tsI|4Gq;WkzXk^G5Icufvr!XR6FKY?T61Y=&C$~jfNCTSDM_jo-`_YU5)wr;g%$F zbaYzdl5PLjq1V3+$J?$=2eum0b)nH49}Nr zJ2HC@1WN5c-z9ApVNJx1`t#QHXYe#Bno@m4ye3X(na5>MdJgrrTWp%X$c!^x8_Az@xL+S9oo6X@ANZcVosK^X-HoM&vXC ztfQu28eNy?*>Q?ee)nl3H_I1HM4JWUQ6ZUo4W~R5kd`}Y?YmeJ(LwfrQ8r<*mLCBr z4+|x!>yh!+@=Ib4Jhn4(U3=&T^_IO``EMcEkHlVFVpqvBd$5WfvCiQ;`L+knCIP)G z@TYu+-+x#-)NSRUcXO|hF@_xn@OnNwmAkAaF4x)oEM{o{exFk%Jy}RYcb0C>uzoU1 z>w=NXU1vLpwB#m{i+fpde5DL6O|j+TsfDOV3hJwjP=tDL7~#YM^+*PO@ROmjCD;_& zceO9_=QZ7w)4z`@rIMr8=5!g%TtMS675dCUs9B0kkO>*55^~xA%3jjp6~mxYM4%cI z#HEA{z^SZUXHBsm@LyymB}v9l8&v8{FkJeid>eAbY(514Q&%T2R!-g^)-jlHJeP-- zL@rV)=h6cP1N22qT0IeFVXK0hOZeA)UBp837uH6zO!JV+2_>YjpdKC6l*+uv3t>uK50Zi(UX+31Z`hlx%`u| z#(}ov+xB${br`ZRiJZb+%wq{JBl3J^?nlUuqkh^7r_85Woh3w~naeL7yUCuj0zT<= zc5jJYI>Aq�ykjvtjei+?SH77Md+bhU8yxR%ta!7|j$iacgF+*M8b=IapRXB?N!Q zBm4P@z`93O=_fe9ZM72mX#dM-aJw+ufPj&1u&*aW>X9M!_?6-+roW}C8bJjF=T27r za-zNfjPu$GH@4d=C8=j|<`TP0HXO(Ja!RS{*`1u>Rw;%fjL=sa!%IPzmFr|XPsXA& zFudnRGHz~?B4$^XqVQ@phgU9t=4ADqAZ$loBW8i~${`{ei$X;45r=Gay{GEeWv9A8 zV0iqP8vX^T2YHJ)Lj~V!0B<(Md7`tHu(FuYYs!#cFG&SUvCaN>-td#-JiN=i!;g_y zE%fAOEA~zp*5o_p+=^;c2v2-9;Us-Bc>d~lwK|M>JxW8LnlHG2Y6EaAAz5^GBnRTQ zrwSY5LhOTpJBL*3$xb+Soz9pC%#^nZlwCuExx7`0M`Xjz{x!;SUM;$j#_MLazAs`j zuLgDa`=jZ!q081BvzQDrwx^C@xj>J!98*kM(fcbCtv~}Fo~&x1g?}q2eA%%H?T-1< z51;4)Dd{W%N7DBAd45Cx0O6xG6wIx3fTG|-g?v}!Fvjn@>Ex>AyD(Q@^Ipo7dVJM^ zxQ6=2-`u?2>8e;4sseo9DTg$8h%=78pR~7(RA)k84yRfB!>QTn@xak%M?M;ZXC$vT zhD2HE$%p_~aD6vA9Q{!^?fOYc{fgV%;`N&%2->nW#Ng}^jRJh)+EM`tILL7b;$PO0+>t4u2mvH1A@{jgQAmx4DSk35W_I%3q}5_?D%wXfiG_$bqYv)GPe3}5Fi>EWdi#CdK< zVi^UUt~EEpu$m4CXtgTG0{t+lH}{`wCZNO)6eq7>5pu0dui zECdvss~h7~710<|&iwr^U-)1?H&l;MA|EJZnW5T!nqq>CKRr+G#Ut4kdw+(u(^eDN zI+3bnUMq-fz6d4z_Q}ul#kIr*Iit9uMlpFGFc5cM5C)8K^TGsWt24aKDY!7%_k7{u z*DJlZ+&U{yR2zzT^Z2sT27!UKM5v|cY&N`H&r2_TKm0Q}jRt6feV)c0koB#9aCpbs zF(r-?yDeH@tLjY^*E894y0YIl%gtSO{_8E5MIT!FV&q!=_ z^iD5ofOiCtUID9}k^quzc!FD0N$WR%gw3JdNxoHmvJ-{K0w%G=vFyiB43lI)$LHn} z@SG>IWJ0lBYr!Eb)(&#*0vmaTbl@w%%Ec3QOFaFo=Mx!Tple9o=j{c7v(sVbv>WF$0i&3|Vl#)Z$2QNEJ zV(IvLx@b((Z7vMps>YfIUVq9+BZIcRY-z{!M$Ff+Wv|3@>Uv=v^!(ZhT2NaIgR+bZ(4bgy* z@Z5QoEc-oW>XusSc!`y@*JK&Z$m1BS5bdOk4Xh=DlaKL~f=AZ8N`gSkQ-|8R+e2)y zgd-sR>JtZGooPhFa!q$Op12xUb&O=Qp%lJXg|gmEv#G3PS7Tr}U*=PN)jJZ#f)`yQ zxpqY5d2PRv`~>+G1qEtb?3hx2-f`BzRq*l6Lrc|Stlu8i`FIP?d5PC4ORVmti7H4x z6^99-n@EO(OMi7L7*iBhDI9?kV*22 zCm5jIu^5XL>(I=V+CDoLn@1`a#lA$y@eLOT5ouoFmc^Sa>6{OtmUKF49JP(^@T2xY zu)?_AT5y09<;uJs!k4y?S^;f)jdTO#-qQvyoMiV;d%c6*9 zW=`Mxi`0bUfc&48q$=7&0ro7{p{I53KD`E-W%G4B_Vbku#c)~A8QwuaQp$Avx3SkW zc#q|(Fa_bYYI{0pJE8*Hk3FLf22Vgtk45;>hwYgCPx+B?G3=5E=IP7^)o>h5Qi3DW zE!fjXmHAnyy&>yas{UVzZFSzLSk=R`v{A7L(MaS%zhN_}&{+1-RPgJyeJ-hGRrx^K z@|`uPWiXud8bO$3ytYGSwH~JKo5YA`1Htj&HyYtj@vL=+IyU#4dpVxU?C;v5P3PW* z<;B$_X~WR&SDBSl89)0Q?zdIhZNtgr-@&FN{$Qb2G+8YItf|5Pli~28%2H3dfK??n zS0)~Zva;{ae^fEIB{gr1Ag`IN2Jo`0=_B20UwO*JoX6VngHyB4AM_xKbgM_@mhU}3SlY$k;|f0xlZqXtM8@*JM@KroqByk zNq-Qos<{6xGU~U942%P48P3Q87d~(UGn~B8BlrWT8CN8$oO)PJ*s-GLO7McFU zLde|!NN+iiP@IZSt1T-VcNdpqGV#L(+5@1EhxYt@6|~!F1tQTkL28UlWg}l#WVI(T zrl8he2q?8hUWRKBCNSaxgV1K)Sg=ILIvjqQ6n&UFf3uty3{C)L>~6UP2wHL^yMgCm76>X@(Vt9pb0)MYkD}Q3%HP8h0#) zj`A$C`s0cZ#Q9d?HS98@fXrAQ%FvrPaW{o>8@}bClY8eu$9kI}+ny}@@&RUk8eD@R zHDH)JvK(SA;llfOzT7Up#(mh6*4Hz}Dw@4JEn6LYuiS}3!*nCr2wg3=htaae1deQ3 zl>?1+m5rT`$^8Bb#396oVS0Boc>wA2-IK}QJQ z5+Z%kr8Qmefco3B25lV9fosfqL5tNf;tRx!lyW{w9Rd!iNu%|bx+}34a;q`Hc=Y}l z!I>fnF0u3E(LLCqdfseLveq>J+YAK(Vj-sv7`AVYmVChW4Jofes~~kP#+}7ct7+&m zC%$7c?cZdzK@&T~vD=eXXi2uBtgYi4HoQ2)U$O2C&uh{S1Y{Tv=f}EZ zA7-iZ1fLCtW!^T^u0Q?eh_IX4P|CTmMR9ESnp?hUgc?2>xCNi9Pe8N0&KgEwvqH=` zV(zo(;3}Z8+)_p#Zt&FAI?gCEn^z>C(R~Zh(oiGe`Y}djZh0C;il>7jS)Spy#09>8 z1~AMrEN~c5NgJ#tNFQ(hG9EUUs!{WOKmel6#tQ&_uU%c;pb%pr0gk9}a z>Eav|B44$5A?IHbfk`ehA?V_?^8muts~f(?{Y{%Zs_$aMa4C*>FK2052t?r<5G4K^co4?n!eMG-L{Ug3|u44;=5ThbI2(@>zQ=0j!<#msSjg-6$124%w@ zPo)T2K>CSi^uN9ox%Kp*Dy`L`!_o9*9RMOdQ4%?U@ez(ieXAZK1UayypI(vN^~T-Sd}lU*+! z!x@l%N8#x}acS9Vgw=P&VMlxFx`9W#%RH1GMVHA|i@_bB!zoaQ(}LI!ErV9%C3crt zBR4?SUY`h%yd4ZzNNlVoEV0QeQxCOrE;=IdTc7o{<#^yjJ)jy?R>M!e?9)WlD?>)j zR`O2u`7$DC3?_g;GHec?!zE)iRk<)1??9eI-@&*vvqxFh=4sE;=Y%>nFHg7F!gui= z4S2Tjx;{9Gild6rpjq_Xl}9s5s^oD(j)8LPT6_8(7{PcoXVtd&HnuBaI22ThP)5wS zL+~NlLD18!Sr7G&U(M80OiVY8T{D8c%zPqQ!gTy1DL25|xB6&!!|%8zwZp_9xK8cF^;x8nE%rS>;5VP9mCqSA^`TBGiXRmrWNqBb4e z=->-rdwr1hrevpy?lI;}IE2(K1Bl&{ejKKz@cUnd&jd!|d~SE*Mu8SQ9z>B=UA;F6 ztmIaPl=D?TS#x*}i(x~GosfRyt?fkf$s&T1gQV#;vzJYV+GQP6Ss>Kpy7zlYUcw`1 zCkq`Ta+AzgT7<$@k#+XibLXtxSIQ7e|8}7UCtlK1Qg|oB@jku-L<{}Cmsy_0Z|kIE z!}!gB_9fNc?vFL>hi~Na^twThwVgRTG*pRINR8$gXPScP@5W%NWoDIIH`|P|__wFX zHHf%;rqoGosx2((7c+7Sn1F5}N-B4x-L-9=903M)W#8meI5ug|FkV-12Gzswr^+Dw zd8=yj9&dLG)An)+hD!_Y%v za)F~bg>-gvL^RHur|dcSIS(VB!2NcbghlNr*6;|Y!iL`kH&!B-)+fHBgnm^DA>UDW zhyZhy6>#vmGuOO1i=M?Nf(#-)trpbt{T=P>=?2}67Up4SdOxRO#Oysi-axv9e+fAM ztK83SCxr(&u|q5nIoIe9^3cejM*K-`h%57rS4s!8ZBW*eib-@oh9Hp{jwr)r)5rh}TJ zLDxKCG|ibQPx^uFIYt&F(_HhI_PA6UP*~Is8%IUS^c!>GLs0wt#B1)wM(6`3?G8?W zs;bP3O!A7%xws-Yqq$qyww}H8Q~zJw#giWJ(PdXwIWh7u>p;FYY1R z)OxIW=~Ky^L5W9)9K%QO@Ajd!nOR04^C*?v;PH#InrQ>sHNr91=gIH=041$dmB~DM zUmxeu!jAm%q$F-EDSi$(Rgdtk-9;JlDb5N81+OJlQFUSUl$*EgP;S&6)4oca+5ua& zfPkIe_#te3zNW9w?vuL(N?Bg|Y~`P2OsNx=pLzn7Ajl)4=dTV-cm)(j1O|jT%k|eq$XLz_-_7~z0RsW_Bhk}u@DYbXq#cONsg`)iJxn% z&i$O8A@Buki9b)yn!-+UqxG3VE1>>%oq-&%Pma+#4xCj6)9reb8k@~ND$pAAm1h&Y74}w!cDHT2|G+g;R6OV!u5< zdaghN9|Gc~x<7~|2>Kf6T4PjI^og1fa>)L||7L0g1DZ|lRF@>~GB~B9=#CowU@t=4 z1)vpo*&6=0vN%|-(^-;9nhESQWC7+z0|0A)&dH5Vt_=XCnC`A ze*U+@92D>)u1O_sg;95Ca2NVt8fj$8Xxk5|@ZN`=*-tzdODUw^8=-r=2rTv(JHxN* zF8~NL>R!TZsOPtkL=~N>j+u6G@M8}uPFjSN(RW}4_3+c@EeK3%pO9W^e%2pIpH=i$ zw^(SiWIrQX`|u-SrTi_6?t}3$P#SB7_dAC-m@3%Y$`_#RCWocz;>m(-aZ?|lJ$c-o zijsmr@rA^4&6MRj^j8ibC7kOc;6!IVS!yix@}j0>iUp5`C(-P2T9YKmY<#DgGIrL# zy`;W^d_VcGzER-U&Ja8E#E31s^R*TKbY41IEE-&}usYjrD<`Rw8tv=H?sQEyf<*0~ zh{%>OI;8TZL^`(XVRH<&pQ9BR^i&#XUsrHT)07Rg4e!$k@t-qVR!e`WYbVQrdo75g z!HV-0!KBV$-H@ zq80BxhlCCMgGwK);%EQQo%CV9#+CoA1^62u|Nm3;`_!lnl=S|BDpml*&v?6n+r5P$WIq7YLXNQMB`4lGGY-86W#rd&#=H>U?n>-07lu_@%}&+#`kd`W2@|3|@|Dqt*=EJTqDCVW!(gkd1Lc zr3vxNZKAULUz+_mdga>}tKqJFR&alVo>dUMmCpMO>=)8zhp*%K3$;4sf6Mr>ZMQ>d zq9!XQm|F}_e?iI1e1ko2jea^zQvOP^y3k|uJ%cV6m*Joqvr8*2fVV2un1r!0M;ibM zQkA$)B_>J`IsfKjFu&=0mXjQiq3EaV&4!gn9{tHn_AdJ-sAb}{-t#99O{9!ST~bCn ze2Ptq59yP^xuf1PA3gSk){3-$4a0AM&WXj)I2r`yFdD6VOYSF%7tP9J^P=mI>@bqj zWnEnX%A9vp%a!}P&!r#Lk)x`q8T;&o{3)Z3heQX-^6nnzYasFjkpYt-pd|rSFGSKly z65xSJT0qF*=Mr5PW0OuK8BKnRrn4s`VoQxiDUjsM)^<9yu^YsD9&hHqdE&xu11eqWzXa?F3|wu#;Fi){M9cHy>UyP5 zz)-GqQ&Z}(WsT6Q{35D?_q1MeAM4t_q%rx|x1Fx~{g*)Bv*>>W`We!ffTM(wZsojQ zYHq3*j^8_~xaow3by#Yx_-q(?Nk7+SKOfaT_47<#`|1HkAibmJkinj1gR%YUWc}c*l&WSQwB@5f}ZvPpF#jt++4g0#3h#e=(BaA)72 zZ)+rL&bg+o`@VkHbng^zOq zcw*qEO`;@S(VWS4N}2j;KGTfC$Ya~7_|mxLOsf9_fw2C^C0Ueo3VUg%JM&G!&~OJd z8`JR8r^aw$h`zVps5ueWU1_oQ8xP%v=a31*;1TwOxuAaxASOK;L}sbRoC{j6OW6;X zmbYdCi{GKviRo#C<31i#G+*-W^UVXXr*FM%{RsV^GFjT<{k})lZYpZK8H96pk&4cy zm>hSE30Gy+OvJc?*+W`5K7Zai6*=@idyqF9F_dE{cDsw`QJ$iqFEx61@q}MV;_82# zd9;2zQd4k;h7ZNFO=r36^1!YHKjE9Jx^!SFtc}#Mgd9jjhd8(EW~Wx|hR7_pG%rV3 zWFH>?I?7^8=S{r5A>ST##;4)R*^h@nQ2eG=^^z94a(zYWT}sl@@S=tJFD_ zr$#?Wbs_r5=^-)?tH<~!V&prlp?FV1J$XQJ=r;uRYR+=XDJF#}=Od$d{M4M_4Pk2O zwNzk_2ZGx;S^mquD9?CDv>0p3TmEq`_!;cn+afTK4iL*q9eJst&Q09ozsEjTCt!&VTceUsf_2@jpOPsrH;sq+fx-I90k zM!t^bg1Y?HedxpI>Vc)M4{e2Tp@=w4dRPvAqme-=2_Aer6UbrJE+!K8J3LXHTH%iS zVqdiVDw_yq?`UE$w))PA_b-g`>6!Kf0sDjI~; zRoOi@T@v?_L%rN_EkelobHS-0Z(J}m$lM=G`7>)1E=U^hti+%)*P}2qQVk^J{fbDL zBRaEj&c;b{Wue{~3s7BgD4@E*rI|^V>-2*B+EFqRTd1BaBSaY^Z&yO4T&0hWecBwD zEdf8No|a#qqBcYczm7L5n9S3e73C9Mu%W@@Nr1!;BfVRn0=%wq{So-WPf}T3a zwo{}=bxG^9m#!n7iWSv-?_Fk;r>jx_D(a@Bb|E1AWZgEC*3PEaN&_D$`Nci#SfcCW z<^+m1CrY6Db|=G@qK+T^$17I`iG}ZMFv}a{H!Pa{{j6*mK98g0t?s7R_FvZ=w45_k zvgNxgS&QL*=iUKS#SsY<(+vEyRM?G=Af>z*x)cfnvy8asD)-!6R@zc=I9J-?4XF3x zB@IN!F>shB9AB;9K2~}?xu1EoG~@CUZ`p{j-8Ph~*UL@^iP}p)UmQaij9#zxe_u#@xSY zqTr!44ycU_w#Wor)(*t~CL~f-wCklJEdDM1c)oI7Mthe!y4~f*0*SeslS>w=YSj9v zu9ji72}xcrni5tb(S+dk*3jFAu1|CwA$8TO>EoT=A*sZmbFQp%g2?4B=lJp5tJ$LNHPm=H3PZ#kK<&40e*g7z19T9f z7#$9%=KU9Y&|L1JWrL_E?#&dZ5)#-kYFA!DOAXx~nqQ+BN7Zx%Vf>4VUn9LdER)>6dH2?4fkkUWPA%Oy^9qh{8KwUWOvQ?w^1?sy42y= ziCQ@0{qvSLj#w;M*0A-j%#mG@nF*n8LpDBdq+yd`7I!1-eNpSZpMpZd5MJ7$hRC)spMp^bn`H3vL9f60P zLxP`xJ#ToX6j4l&R-EI}#628k+}Q}nnN-3RJ5@0#UP`=;|Bs;F!=0X8c=gA8l_H0+ zD{m7gMA9|`7G{)psG@#P1j(YP-&IdXVsCUv7qij)?mINJER9e}=LKnWq^cT5my1rt z#7NOVbisD=-hv>8j?BwgT*!PQ_JX++_pCMU34hB2tu~qW$Jw2B*NvH5#FT{QJv0;N zH^k$1hDiawEZ?&uV>X zSb`jFb(9&biHaQ6S5K1Mt>dcCT{|D!rClRmCU+2dY>v=wgtxF%(-}4 z@nplY#x`Ki)LSh>FB2GqYtB~*FMv5o{|!$*PCBQ3#rl|wibY#HP`{o6X~gVo4up&X z6+c91JoUL%a8JB4Jo52#=DfuIk7(c2l-;TR4{HqGmQr(gJ1Vc{dc&TC0sReUNH zpvaIaosG-2LHaU~Uf9hD`2D9e|Bz}qGA{518NHerx5JpKg)fHpL8Z?=)2#JFcR+*o zfv|*q<33(D1!}~ew1gKz)vzTGRg%MRO_ZX7(ahT2NC094ia{fIC+4|a$c3)a_tJy3 zarD0oC+03hEV$7OpO_;RmY_65LCdrO4LRxz_;9(oAgX04O*UZfH0`SQ?*q#&;G0W>5Nd z_F=V&qRkMyE2cKC_@o4Pm2_lI6_;dbY1>ykfsz84#t{+F|H?)&ZlUUuO*jz(g{*j! zAU<-w8gY}OkKLEBKDqg%06;#Q8cg9wG&%Q1Z|>vktG z^VZOLEZEY1l?}udDMJU6l)*ovo$O`NUONAV7fV`WzYj=h8J(Q2ftj!EK`D;kQeSDO`$zIE<%D0XMV6c4f`V4&2n! zzG{=jS<@<>yMvrRf;vg=xiWaC=s=O~^JDbVEP~q)8{zbPqCo(L&V(ZjEAKP!Y4i6s ztKGjb+6G6Y8;uhIrmqr+n^)C%OmtK>l2qdy?R$%Ew*)=ML3gS23M!bT=X*z`VUz?7 z0v@Iv7Vo60RC!8#pampX+ZR(sj8+R? z+2Bhj?iALhZ14l^?CA2r5&b)bN{4+A;E}u3G+Pi!_s=VhmKzF1DwUNKDfg15$*u4> zA}iLo!wTJAY^;OMbyR!}gxLkPXO0^1xZ<_i zkpPvduniN@s_imry?*Qg(~7fe82y~#sdMyl&bt!90ITwHQOoS}1c5SH3Lwg&2|ZvU z{R56=dD!K0Y*m$*%xmYfx|;jogVL19T2xy4Y6gbyQI;Ig-n2ESW0_Q7fgH_Xi})a~~xX@6?7 zq<^=U$P*IXvJ|I(7TGCxCs8#g-l9Xq%=_+H{|E&sH4(Eoj)NL(jk_r^sOaO!1{ud? zqR8t+O=9?V29+x{(-|UDAD<(kWV-$CZ6~!Km8UpzavGbw9$}L$VY6rc*(0EGcsq%( zg_-NKK4VcFip@v>!+4;EBU6=J(vS%;?5M-qEYK|!QJ6*?i!Ul{n zP$ZP?V~=0#+43A$!==ssm(4=|oHL;`G@d)S~`D$;VrA{GQ#DUc8(6!7>-ND-^*6`=LyajLRw68~)D zf;aX9g&8Z)QNGKgSW`OWuWXIgIPv?0J=ay{WqvUNJ0@PgA*wgKqSUBEzfdo_mV#rn zj1W#u%zBnC$~)Y&?Cd>TVrfBBd)tf|x&cFdN!C3PJqMR>W0JDiQe4poR{Luu^OpBT z_Z^uCeWcZ6|35qg{CPiCW^iO^na)^rKg_z$DwZ+#AQ|q>BWs=VB*|l1dCjR+fQolU z-H2gDLF7wu)Q+Fhu@3K~>F5X-np+{%aKZ>>BoMo0)Qqe7@5 zs<$Ay(x8UxM~GEFgsH?=nixwu_H*p*UkPygrI@K0Z_TvFN1;Ku%xS9fXVVFQcm(gx zX{sB!h&fkJbLSS&_z!!E{*wOdz~=K)0kb=~MGRRobcKb~NypFdUeQrb@ICG~M9=-^ zGjGVz4G_+MVj>%Ch|Ry*)XJ%-z)$ftt;_f&k@jm;%y5V(>Bf6}7MhY-5$MvuJ`+6U zJS^8n7xGlYn59Z*kKxE^Y2`fJjD1E;68TQ9>?QLPpQO`vL0;Jhh3BaS z1BOJz6c%3{{81f5$F0s!k_Sb}cd0g@wosO|b7tL_ziD`jr!Jj=rf`$zvs?m=DTHg3 z3V2m{1~iV9ILRw4d(fv|(N2kdH%AnJNIy-&Fo+1#=0n<*KsT~v+sTZBFS<>fZU$X; z@tH1{9m7&;`Fml4pN(HxAf*Dqi`^GAP}6g3G~nsRyYxo9P0{!AhbN8Pu*{M-YPciT zAlV+R16AjEYG~idhdaaL9PMOkifT89L|3VPUFolTqqwARfi0POQr1l-t1nltzC@%~ z^O&;JxeeVE)T1^X6FGZWOlq1L$+#Su5-5|~jhs@WUbbS{_~QUfD$?fDH6fFnR1-i! zyqmmXOA>>@KpJaH@A<;RnL_RJ?}OO;PmTi~)4o$ggb%yKiCbxl@p zAo89O7YQPRHow^sbam|2dhbE;8;_-0QXvk-dmu00pbpCr6wX7X7ImQ5kN~|P;aXvK zo_`~#Goh1Kyji6&wj)hr$!|J-tbR2%qud^4H+5Rn$f4cnYIH|cQbE57LS+4!(c8+N zS%TZw7w(k1B6t6+w%)Z`}rD?oVmHer~Q=9TaP z_Vx1UC}4=Y$Ln~8>0Opk3|^NvX%q#2Yi6&9EDC-m(~Xh)i;|>WM^`7Tg}S z^Wo#%G8;sEiessgWljet7%&h>V>^Pr^f69F0&LSo43hwF0!^HpE}e+#n#v10bc`4% z^!fs1md2fU2?)=UMCf;#clFxB?WhX2^J@)*jIuwom;4wiTbxo+r5 z$z$!Te8aH7X8%pwwIj6{41K2J{%L>V$_OpQEB9wv`q%nukRcczG45~8I%C; zX2)LT9io>acF?KItH5kgnbk$+t-eFVyA{%-!iuqmjn|V;2ph4oZJx?jU{a$31shtZ z^IJI1-5T7>7E{M1D89G^wOC)AitKvh*r$;t8oEiI>8oINh5pu%6mUO7UfnS@OZs&ZtLehQcu0nW_gP3ilzOEwWZ0VFDgC!Qm!iY#syv8r75NlW@Wrk$a zLRihCvIde%Le!5G-5MBEeBZcdjmyUS-1EMx!Tfun)43_6_$($i>+-ZJ_cS!1T2pa} zDOrfpS^eY6a|OCJ%M9xhntP1so2YAi1AXl0;QY5hb>saST3U5|QiqCHHdN$CzId`1 zkxqW2_tG(tIo(L4c;Q)l>J9sjw~Ki=*-`g9LuL6|)$$e}lK^*z@^N8_*YI3j`wq2c zCeH6Yp*5_`jdwg}o1)T?_)E?BO^TUDOfjNWOVJVE$5 zRyL}C*4ZxLFuu3J9FW))Wgd7NiC_s%RZ&riaArS*jm{@Iw3%9x46`ejdgsG%vgZc*1&zZ+(m#OXF5q*V1=$xS`V@Brt9T2o+Zl+auAwH?x!wR0T zz({2yy(>|%y~pzK#mMXxa4&^7O-DDMj{(*!7fGrmg?>Jm_!^~B#Jx9`m;$J23|lg_ zhoV+obkx+{H_dVgv5yclNZ%w5noxf3r!UTGrSK}mAGe|lKF4wPLWxm5OJcb9`C@() zZ)HE>;*e3n5CNfA2Z2Ik3o#pV{A`Vj^AP|@(^$OcnC@AXGuoE-Xpt1DR#Er=XaO$G zb&ZRNt~+mx`Y-949@Xkv1hZoAqt*Y!{LHzn4_~P{CTM8nqw)DsnzayHR-$LeeTXg| z@nH+78Zw3&C33cfteoU~7cR2!tZo@ru+GJAy-IUhINDf#j5VgG9GeyB9yc_%M0_AI zFLRN5nISxg6l?gCZ#DYPh06#Jz^4)QRO>!MN+ba$#Z#tSSoZ6ouIuiYRB#S5)WrDc zGWSPv;l@~ROTbupQ_Dg~;aVM}_{-XBON|zd^-O|1Ml!qKcgOC% z8AR!5KpZ3)VaG$4ud6lkevqzvRDr|~2J~EOs8d_icCi3KZC~p+)gfZLrnO!i*ajJC z^`iFlCy;UE3Ww$+x2fv%ZPKABSaeak0`3=6Y~K6%GZ(gC0Cug75Z`MId^{DyUg56k zS1{E&G*_nE;7kKH_wq^Hvx3)J#t6EiG=LOeP=C<1uNL83_L8JwW>L6@h|*SAG(^HM z$(%?%Wbm7_*h*NBy}b~gHHiiP*RBq7aw@ETUrle4d~%{`*MRfEa=3q!@n(;-w!2Yu zGJ)vN`59J)kjC!`CcbVj;G|QibpS|rhDAg8#t(OrOeAOz&3bTFRkidb=Fs9Yb%Spo z_F2)ai4u}Ug5m>a2lZh9^-YWLL|ia^bR2(zE0I>mr!n#j);9J8eg~niA`y_; zM%=Nuu=Qpx1sa{iNQKDIRUx!J`h$_(y3u?RS+bRQux3U(>Xn-+V!Y@(8y{iciPEx} z$cbO?(vRNDC+iR4bo7o~4kVPcb-YU4;*A&^^m+(Cicew5$bjB9S96sf0+IP-JS1`F zMV!|QsY`b}i#)n70ObO$m-WAI9Hrs>glEf7B`O|!SG+5k%3|Q=D_$?9LkIrF>OdI} zgOW_5A)TdAM!y3Q{4V!*jqRsYTk_6&GgU_k0sEi7&7xP==`m$2@Rs5ai-3;Rm@BWK z0;)Sx{8tXYDbjg_-l%V?CI1(I(4S-Z8y^#&vd`#s@fG{|@z-7b}$t?xzIW5;(0>v#8jc_@#$~ zOGYt<@V`%(KKH-BrXNY)$esRpvz8SkPjs8$wXoshlDcH_Djc;8Ldd>l@vh9oH#DNk zhlK%jPv+1sg5`d97usH_0^wb?Ul;D2J+!bs7gph%Mg@xN1QJvf!c~X86Q?IZgyZTv zV#DmM@pX~j-t+jwB{%cT|8!r9MhrddngEbLi>o>n!>cgXE&0WpJ21WQdFK3hvmukI z9BEr+qhr8}Gt0x5)oVHa+E@IkTQWdLDLyQ`o72%ve&B>@= zt7}0tjtLJ{sW`-a_zRH_RYC7r_A(ip9b6NMeXc!uFsF)~RO(2z?8)h!!lWd)VtFYt zx{W@T1_PL{-1euN3h&$!0IHBgl!%i@72krWsF-WnyW&3{$B^yjtIj8v4<5f6yeuHY>^I@;jP49 z96PXUaHzT!;(d5sT$de{v=RsN+$wEDSdgy*(*eav2c!Q66!%9AmT^TBp8f z!kjfF>zgxZM~mivt77(;sVs6FKag0%TRXA3WlYYKa3EnosT3#IF{EHE6Y={%=^{ZZ zk!;rdCI&bzfOE#^Q^u1@tKzYlR(y?ce-GOz9Q}0|jLrqRqow>7ZIVFbm84Vk-1oR} zZ_jYY^rG=ucY`gagVBBMO1*IE=TBc1K&&HL8S8k*rXLlPG6qaxQP=eOrdYC7W0|S z|1GJuENE@wylc{wcN*xO7=BUL3UlGcrH@T)8hCVYGzh*VZ%tn6=uEAfuFQ>UC^dfk z*FOmrjY5TlN?N}Rc*$E*G;>Aho1L@agi^}pUn*37Z%@!+UW4~yHweI`ee_!d%Tx9| z;U~Sfvd(jD^sAM`x6c2~BpraC`od{T!W=Y|Q;|)oZEM!+lNp^i(n|rLZlr<8vn`&* z;O3l$lHNVh21>7YC^k@JpUvz(YFqV;p!*O4S~o#GF8ZJO-cPSa+kW=*wB5&+#-&J8 zCikUsBkhX*iSP{taneG(zaZe3%ri~({9H#Khh<5w#@$UpS&C_?BJX{aY@@I;MGf;1 za<%Zg@%Q_}KmSshXKD}+> zrtYH^ofOpo`1GfE3LUp@h}I|hqB7P zfb!{p;JJJmFnWQPcK9H8ICCv+Zr&QYoH;SDU6hX?R!#W>c09rEFW?2hB#(7Lilu)S zu~%EYEQNIdiBlb;MySfe=}LEHhhZvPB0x2`e)zYqb?w{+4_4Qikmf{I=i)aQF-drWF0W$T@Evz~&L2Z=i^;Xq2*=@eRPF7TWyaQsf2+-aez0-$$zf%0m zBaYLU$CO^Z=mG3|a0f9@=V#L&+<(95AM^BS-ca>v)~v0M1ZM4D(F$F-XZr;8Y$oSRWmLdnu-RjlO;2tpZoeD`Nw$JCbl?P?m^PH&W~+Vy-8Uf0-6OtTLxf6 zo!88_oBnq3sftNq5@V!he}`nsR2G`fd1K4s0hq#}whVp?RBq25T_C;dWLM$T)Ds>1 zht;OrFkfjl+J%1I81}AcO@^r1+%vFkwd$V0=@-pdCG=(?rQe{`^k+yMd?o{%IKf3c z_9{7BQLiIN#U#k8JhbM;QqDbAtF&=xuHuL%$>%(S+%@})@+fh+RiBgJXd31Xwx@Nf zAbf2bme;o2c~gwy%z1cpk;#kF=Avm6EWEaEQGh~%3J(}I2h#q}uNiBR@tDp*8Pf?JsGG)5g;_(=>=n`IU#m6xmR$P2!*;Lyr>|LiM(DPucizD(Lqj1Y)luiyK z_=qrAPdYW4m;Lwb4sY+p0@7@JR7QyjDv$p`ec(V)5HQ#?Cc!b1$`$8WaJ$5)i7c7u z;ErLjUu*4`lbA@4H^~%7-Mj3yvH!(+3TZtdyN`jlK7U$ z>Tgy2Kf+>5-tL()Z-qaR@?7WjQc7M(G=>>UAS`^OAd~yh6okCaGUmUihzHkFMfZxK zRkYhbTDWwTR@m@n0cqWDPlx@^WPPjv{zJ zz^~(?Kd&R&k4p2@{C9=8Pzk#Gp$D%IP;B%Z{l(ZB<$>#WVkGA&);nvywQMo`x+rRm zF@IV)Yhs6m-K-heRB!*51g0QO|4Kqgjc0(aW?W4&A9sGsFjMiU#tfSU|#UZeG2 zKAM}TKpDyj<3ooohEmF(EmE&@0bC9}k0F_oMpCoP;h(*`A6aB< zR6z@C+FkzY*MC`gvyM=f-MQU$YR>&D)f*c4CO04YKV%A>9{awosFkTC^SU<}6*znv zYnbrvsw3bLt0Q>0I?_J&Gzj24HbuIxCJbXvGE4gDaodoAfh-M?K8e- zBu~=;@s)-5p7s%(&zLsFXB*nR&F+v}3$CXn6)jB@0T^eklF2kdaCF1KaTD{L&gLil z*!G6Pm^GEY@LG7o$zApDgbB+k)W!n=EEJ}h5C!vVu1Gzjd`H(n;x|zbi*Gfo}rR0mGYzyINGqD`4BqKwk!VWv2} zs|egw9*mrH8;_98^0E~~&GGsAVKWqAbD#4ERSFvw?Ga})IeSCEF;^glb_;_{M@5G) zzdb0-6GimykouQvB-w0J$G0i^YTRr2zQ1?7=;cF!0lmFgqrTr`Q0+u zE(4R(giHAd8+~hD!t(Yj@Xd2q$Mwsqvf*=I|7woM!)fItbUqvAoJrY`hz9G|;Qo(M zKhU*%6HeYard#?gL}_q?^ye z=~JAkfBWY9L-6r3zU>XwPc#DN@BQqK>#q$#*^SSYuF5XBoyGe}3H8qvk2+nK$8G)x z*PE#{!v$|^*+T-wUm$-nw#RNR3Ia@*7uW}c?F~4m#|Ti(R}Qp@M#A-?N9S!&CAy4=+u3*_bZ?EmuV1#&;`%ZnDXQb@m+4%HTci?75W%hu8) z6SRW4tkW@$-#1xR+Fi)YVm-q!D`TAu`^8IaM&noWNpTE)qdWoeO_SkGtAq8h-;DP5`=?yIPuY6Ayl&A+SQ3U*T0BAR5>@0`b@Hd0 zqtnuFYS_p$ko8L4zHJ~%^n*)oJstO~*^e4oiQXxoh)cVY@r)7o*D|&2NtSfH`|n>1 zLS(9-M}O9Q^f6G$E$fc6U>UOh8)UTX7kA6u)%kXLLD&da`t*P558q94U{<-C(^3mR z>6F7%KQ6ES^E7|wUKQkuXe>l~<2KI)0n`*_zMsE#lV!3hzNEL_hZktb3eU=u_FH1) z&(J8nK=d@6>W#`1cbRsDjnP^pCyJsD*$oTf2{rL=B!~h_(0>o z3n1!_XabD?zc+ys0Q#%$@}lQn@dg#8WOHa`=KZaXoy{Xw`H;Z*C4mMbH|V2YEP&|pP6up$=870X+ztk_UzVt(+T$u+oq?5OV1I(BWGOGmsJZ3Wdy+@^Qb@Ri(*y%WN24G$)!!QDn9z*)d z3mf|nJ?F;b$%1ut@GKV5aJoVhnOXjLBUF_FVXP=Tx)LZcI6^)$*@nv@YO6J)I`qq9 z$y1*>}7o0Sq?jz(xBpKNnW91`EI$>VP4?zR$j;UxNiSOvJWuk zqz7L?u+HyL4PI@Uf6w z3Ds^Nc`w$Hk~K{QFp5f^w+NS}DU^muYs2%ev?=pDUEE6e@G#ouI<;cJjm8}3*vE6) zzd-CC+ZSr(SQmsOf1_pp3cKzx%kLokDh8;tD_bmYnkp0Sebo^NUdEtu;vsc5Su{D4 z`}EH{Q)^A|Zp&38hKpO63%9DhB6|F%M!Q56vjIVTa zlW0)f8V`j~uA@Pyc0Q(DxoJ(ko_!)>p#v%Yb}YQBeTfb~aY0xP`HxPZpjN#F(I+9e zh*x38m4jVg=i6(_|J33ym5Wc<|I{M8Wu2X&QN&XqqL%6%KA8&sTjV-j*S!r%&@3Il zI&9HQbv&p&o>}mS`|Cfm77OpTp7L8Duv#MewbaotG8JOIII1W;snYy!`FMHC9=}AL z-AABuRmCStl2?1&6(oAV?Q_NZ+{tS>yhj&t@De_3F|nES-oSb>WlyLPhfZSiIl7Nw z6QBAKOBm)t2@$h&b#ARur!(Ca!IzoIeH7TAhH%vMxER;OJNwiSQJ<70tS0sAFD>26 z=uMJFsjwySCXF7R8$)5ZZ%d+-m)*gVUl&M8oNVpnSC-p>lC-jpD0yBaOsFK{{7kP_^KFua)q%TT;G2|#AQyL)Q z3%#(g`z9Z*u{5qUK>_h3_0exN$NV$9gWx8P3rx_^kIPO zz2d1LCV~3pK zjNY$%N}1N?wJfKDD*mwA(?2j=h@K(~(Rgm1=@8-V;@wke7AE;I-=W^qAkxIoCi=-G z6f2F|g|AoA32#74yH;Jh1yq~J+~dkhmswSODeY4#$DR$7wcMuV`VT5H!h{Z|*}++u zlMlyzN*?j$-gIRK&yr_bJYF8^VE45Os~ArP#_bh;E`5Sw>Y zcx&#hkCs`!djSj?>upaN7Zmj|=>ENmHCpEr=pZT*t!%iLKQ4(>FOXml5+7g>CyH%(6=GHN#LiR{8X>^&<1|7lj`ZjZfKX% z$PrWgLg-lZ=J?AMxVzrKmYgd`%dU+5I^C>W@v`U1OVh`5H5FlAdEflUk1Vq)4l3|m z4F%GqVYkj;zQ=;ZfcppgoeaH_gZ@TM?zU81)3I!_0x+IfexOr23;v}noGjO#e{msD z(BAiZ8D1BwrV=6cC8-V1OpQv7XK#VyRYC`Xh(rYn{m#D zDHlJLAG6VGE!(HbM37Fz3c4N)%u!~cYCX5G@Hl)8KU@CmIyU3h67gQ}zI=aa<2s9D z=y$bZ>D}q9rm#i@r;n%>Fz?(Iyt`n*70Y5!6VliF@!1$^^iG44Bjmbc?wD^l%8$B( zXpKTM#Cp9uTGN!(kXx)&rFHk$xaEGMzq*^-l)2+BTXtWJ@8^U&Os70wr+!}9^P7{L zO`kuUP6JCr&{}%}YF%G-+qhQ0P6U%ZWWiz)Z^;(P!z`L9eYz~2&Qs3gGN;>CPkfXc zsE4T@>%+pr7k!gqD`AT6`_p)Trti#vLO!=+6qjwfJwPXM|rCZP0dTr$QBgsQQk`4rQ#$9k4hCi zMU0E7zVL0$_kwl~jt9J&UQ>=v0ZO7?E|1l#a+lPip%V=G&kCc635Q2@TXPMZq@J>1 z=|>BV;;tn=gy&X|Unmm)dhDKbXgGGCUXTF3_Z3Ha9OKtXJ?AxcoOrGYO`i~Yqd$)K zB*XO==4BaZbv0E*6Z3lbiuYoUg3#fPCb@?whmHMG<2czy6Rbpe&=Fo8SL`$|4v!Qi z%~|~+uj!Y8033t+s+{V-?Le1-_CiNdO5&9wAgb8=!h>d`1bgbsQ>LXe(+nIlmEsBX z<1B^S*CRrkuNC9w!Pi3G6eg%(NkEV5+viSRvxkwQtgmk&KCl-Y>!*1fi-Bhc&C?k`>LYM+$?`7P39rHlcjrai1?r$f5dUG7x; zZVT59{g@c<#4)I1=>P|>E4>8kB8_6q6@cShLl+w&6-s+!=R8X?dFgWi4Mhv_F(xyL ziNg84bGM^HSLGM_&>fCT_2cX$jTYM*qJ!LQkEsIL0~<)0(%?ZMVC>PXi9?T=qzG(e zpBtFDynH+8!G=pO(s%yu)>C%3zSlZULnTjt><35gKB)u@=^^2p@%MTCVcypqM&oC# zr{DQ)62{3?aoyQ5RLfGOB`xacua`%d{nvdy_^L((k9V;8yss}wJyVkYA1#1Rx%Gh= zpo`nIPqeSrlz?{2+dKRqd|tP6)A*;^#7Qz0bNZHUuByr%_(_RYYH-sd5f2nbwbiEw zy+uX5bu(3l%0-U7(M5TfVQs(2x%jz7sPmkN$!6a8@uuWp;+yopSSpi$jk1csD!tGm z({uv^*%6}jja2SMq7ndJdimqw=@nXI*O*2UQ*y(D*qmCQQUb$5vZ0V~L$Y!(G+ z?@WygPBIBn0aUUw07;LpfM~o_QN(S-ob)kdLcZ>Wnt`4o)@m{#sm_*-X?!uXtg&TT zT$EWz?#ZX$iQuLk(o|^-9Vzud1xb_ODNytp?u<`j)=^s6RRR;Yg4u0h2Obxg8UfkP zdqy*bM2E{vO|CDF6X#5|XD8{rez)~p$epE)E;_Y;e8o7xIo3<9+te7M@cM4+g*%0q z6x<0T)i8dV#paYhMSa%IRc6Oe_^?#vRv!{LY*Aiv+8XqDykL!qM06RQS*Y=2b+nn4yW@CSE` z@0aOyDGi$p2oGMnGm+)ZZ@Y2H05mZ*9IJL62X?&ojKrnka0HEDsQ?pxwvIl|bJah) zoF@fdkF44*Pd|M1!s?PSAR2u#bru1ctyor}sUIq}W^~Apz0R6ONfYxd&;a>vItyeVzyH!UX1{0TLMhy5{R8Jtdm`E=2;k86vx7iv zyYo^TFP5uFWYC>O;V+>Tvp}wTV7#h)Zyc#tTh>4$=6*+d&W7-g2E6W8Q*`L`o4rd1 zE$VkzTgL=L=J&`(uhLm5l*N2E2|*B^PidggZD*^mp(+7-e1|t%S7rv)i+8T?I=^Ta)7K&x$I=AFPUu9Kw{b=nh^x{V z>!RtMw-3rJs+#_&Ma;KshA{@auez?rDv#%>MbogoWkpzK z&eO2ndEjwY&fM2YBi9WuU3sZVtryc+Hpk69-OgotDNU@1&e1c%1-~-!?r;Hhi`m?I^hYq@cT|>l?~lH4;NI z6+>gl59m91b36dCvZ*SjK&ht$MNyZg8n#|VF>}T(jVI{u4HlXxrrhyw+AEbWWy4o zKK8hB8kD-!IB1b&TT2)s4!45)R`tQj`xVg@wS}he17zL_7iEd{2pu T=5%$Hb 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

      hdSn#NF3#Wle^I6%bQ3{S4?kvw(OU}i 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 form. `c` maxes at 100, so >=101 is dead upstream code - # that only ever appears as our own output; reconcile adopts it as a - # legacy fingerprint, upgrading it to the marked form and capturing the - # correct pristine (>=50) snapshot - without the spurious "anchor not - # found" warning the marker-only path used to emit on every launch. + # unmarked >=101 form while still keeping the old t===0 guard. Reconcile + # adopts it as a legacy fingerprint, upgrading it to the current marked + # form and capturing the correct pristine combined guard - without the + # spurious "anchor not found" warning the marker-only path used to emit. with tempfile.TemporaryDirectory() as td: home = pathlib.Path(td) idx = make_extension(home, f"before {BARE101} after") @@ -139,6 +164,26 @@ def test_legacy_bare_patch_is_upgraded_to_marked_when_enabled(self): self.assertTrue(bak.exists()) self.assertEqual(bak.read_text(encoding="utf-8"), f"before {OLD} after") + def test_legacy_marked_patch_is_upgraded_to_show_icon_during_reload_gap(self): + with tempfile.TemporaryDirectory() as td: + home = pathlib.Path(td) + idx = make_extension(home, f"before {LEGACY_MARKED} after") + res = self._run(td, home) + self.assertEqual(res.returncode, 0, res.stderr) + self.assertEqual(idx.read_text(encoding="utf-8"), f"before {MARKED} after") + bak = idx.with_name(idx.name + BAK) + self.assertTrue(bak.exists()) + self.assertEqual(bak.read_text(encoding="utf-8"), f"before {OLD} after") + + def test_legacy_current_marked_patch_is_upgraded_to_metadata_marker(self): + with tempfile.TemporaryDirectory() as td: + home = pathlib.Path(td) + idx = make_extension(home, f"before {LEGACY_CURRENT_MARKED} after") + res = self._run(td, home) + self.assertEqual(res.returncode, 0, res.stderr) + self.assertEqual(idx.read_text(encoding="utf-8"), f"before {MARKED} after") + self.assertNotIn("anchor not found", res.stderr) + def test_legacy_bare_patch_is_reverted_when_feature_disabled(self): # Migration-table promise: disabling the fix reverts our edit. A legacy # bare patch must revert to pristine just like a marked one does. @@ -150,6 +195,15 @@ def test_legacy_bare_patch_is_reverted_when_feature_disabled(self): self.assertEqual(idx.read_text(encoding="utf-8"), f"before {OLD} after") self.assertNotIn("anchor not found", res.stderr) + def test_legacy_marked_patch_is_reverted_when_feature_disabled(self): + with tempfile.TemporaryDirectory() as td: + home = pathlib.Path(td) + idx = make_extension(home, f"before {LEGACY_MARKED} after") + res = self._run(td, home, env_extra={"CC_PATCH_CONTEXT_ICON": "0"}) + self.assertEqual(res.returncode, 0, res.stderr) + self.assertEqual(idx.read_text(encoding="utf-8"), f"before {OLD} after") + self.assertNotIn("anchor not found", res.stderr) + def test_legacy_bare_patch_is_reverted_by_master_switch(self): with tempfile.TemporaryDirectory() as td: home = pathlib.Path(td) diff --git a/tests/test_regressions.py b/tests/test_regressions.py index 33560d8..6aecd98 100644 --- a/tests/test_regressions.py +++ b/tests/test_regressions.py @@ -12,8 +12,10 @@ REPO = pathlib.Path(__file__).resolve().parents[1] -OLD_ICON = ">=50)return null}" -NEW_ICON = ">=101)return null}" +OLD_ICON = "if(t===0)return null;if(c>=50)return null}" +NEW_ICON = "if(c>=101)return null}/*ccwa-context-icon:t:c*/" +ALT_OLD_ICON = "if(Z===0)return null;if(U>=50)return null}" +ALT_NEW_ICON = "if(U>=101)return null}/*ccwa-context-icon:Z:U*/" def run(cmd, *, env=None, cwd=REPO, timeout=10): @@ -184,6 +186,115 @@ def test_windows_thinking_launchers_resolve_cmd_shims_without_shell(self): ], ) + # CC_SCRUB_ROUTING clears third-party model-routing env vars before launch so + # Claude Code lands on the default Anthropic account. Default off (env intact). + ROUTING_KEYS = [ + "ANTHROPIC_BASE_URL", + "ANTHROPIC_AUTH_TOKEN", + "CLAUDE_CONFIG_DIR", + "ANTHROPIC_DEFAULT_OPUS_MODEL", + ] + + @staticmethod + def _routing_env(td): + return { + "ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic", + "ANTHROPIC_AUTH_TOKEN": "secret-token", + "CLAUDE_CONFIG_DIR": str(pathlib.Path(td) / "cfg"), + "ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek-v4-pro", + } + + @unittest.skipIf(os.name == "nt", "POSIX Bash launcher test") + def test_bash_launcher_scrubs_routing_env_only_when_enabled(self): + keys = self.ROUTING_KEYS + with tempfile.TemporaryDirectory() as td: + fake = pathlib.Path(td) / "claude" + capture = pathlib.Path(td) / "env.json" + fake.write_text( + "#!/usr/bin/env bash\n" + "python3 - <<'PY'\n" + "import json, os\n" + "keys = json.loads(os.environ['CAPTURE_KEYS'])\n" + "data = {}\n" + "for k in keys:\n" + " data[k] = os.environ.get(k)\n" + "open(os.environ['CAPTURE_ENV'], 'w').write(json.dumps(data))\n" + "PY\n", + encoding="utf-8", + ) + fake.chmod(fake.stat().st_mode | stat.S_IXUSR) + + routing = self._routing_env(td) + base = { + "HOME": td, # keep reconcile away from real webview bundles + "CC_RECONCILE": "0", # do not read or write any bundle this launch + "CLAUDE_REAL_BIN": str(fake), + "CAPTURE_ENV": str(capture), + "CAPTURE_KEYS": json.dumps(keys), + **routing, + } + launcher = str(REPO / "launcher" / "claudemax") + + res = run([launcher], env=base) + self.assertEqual(res.returncode, 0, res.stderr) + self.assertEqual(json.loads(capture.read_text(encoding="utf-8")), routing) + + res = run([launcher], env={**base, "CC_SCRUB_ROUTING": "1"}) + self.assertEqual(res.returncode, 0, res.stderr) + self.assertEqual( + json.loads(capture.read_text(encoding="utf-8")), + {k: None for k in keys}, + ) + + def test_windows_launcher_scrubs_routing_env_only_when_enabled(self): + keys = self.ROUTING_KEYS + with tempfile.TemporaryDirectory() as td: + temp = pathlib.Path(td) + capture = temp / "env.json" + cli = temp / "cli.js" + cli.write_text( + "const fs = require('fs');\n" + "const keys = JSON.parse(process.env.CAPTURE_KEYS);\n" + "const out = {};\n" + "for (const k of keys) out[k] = (k in process.env) ? process.env[k] : null;\n" + "fs.writeFileSync(process.env.CAPTURE_ENV, JSON.stringify(out));\n", + encoding="utf-8", + ) + shim = make_fake_cmd_shim(td, cli) + + routing = self._routing_env(td) + base = { + "HOME": td, + "USERPROFILE": td, + "CC_RECONCILE": "0", + "CLAUDE_REAL_BIN": str(shim), + "CAPTURE_ENV": str(capture), + "CAPTURE_KEYS": json.dumps(keys), + **routing, + } + launcher = str(REPO / "launcher" / "claudemax.win.js") + + res = run(["node", launcher], env=base) + self.assertEqual(res.returncode, 0, res.stderr) + self.assertEqual(json.loads(capture.read_text(encoding="utf-8")), routing) + + res = run(["node", launcher], env={**base, "CC_SCRUB_ROUTING": "1"}) + self.assertEqual(res.returncode, 0, res.stderr) + self.assertEqual( + json.loads(capture.read_text(encoding="utf-8")), + {k: None for k in keys}, + ) + + def test_launchers_expose_local_env_injection_anchor(self): + # The marker pair is a stable contract: the Linux deploy step and the + # Windows build.ps1 splice a private env file between these lines. + for name in ("claudemax", "claudemax.win.js"): + with self.subTest(launcher=name): + src = (REPO / "launcher" / name).read_text(encoding="utf-8") + self.assertIn("CC_SCRUB_ROUTING", src) + self.assertIn(">>> ccwa-local-env >>>", src) + self.assertIn("<<< ccwa-local-env <<<", src) + class ProxyRegressionTests(unittest.TestCase): def test_proxy_exports_header_filters_that_strip_hop_by_hop_headers(self): @@ -255,12 +366,29 @@ def test_fix_context_icon_atomic_replace_preserves_metadata_and_docs_limitation( self.assertEqual(after.st_uid, before.st_uid) self.assertEqual(after.st_gid, before.st_gid) patched_text = target.read_text(encoding="utf-8") - self.assertIn("/*ccwa-context-icon*/", patched_text) + self.assertIn("/*ccwa-context-icon", patched_text) self.assertEqual(patched_text, f"before {mod.NEW} after") self.assertTrue((pathlib.Path(str(target) + mod.BACKUP_SUFFIX)).exists()) # Idempotent: a second patch is a no-op. self.assertEqual(mod.patch_file(str(target)), "already-patched") + def test_fix_context_icon_patches_renamed_minified_guard_vars(self): + spec = importlib.util.spec_from_file_location( + "fix_context_icon", REPO / "fixes" / "context-icon" / "fix-context-icon.py" + ) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + + with tempfile.TemporaryDirectory() as td: + target = pathlib.Path(td) / "index.js" + target.write_text(f"before {ALT_OLD_ICON} after", encoding="utf-8") + + self.assertEqual(mod.patch_file(str(target)), "PATCHED") + self.assertEqual(target.read_text(encoding="utf-8"), f"before {ALT_NEW_ICON} after") + backup = pathlib.Path(str(target) + mod.BACKUP_SUFFIX) + self.assertEqual(backup.read_text(encoding="utf-8"), f"before {ALT_OLD_ICON} after") + self.assertEqual(mod.patch_file(str(target)), "already-patched") + def test_patch_extension_avoids_bash4_mapfile(self): source = (REPO / "fixes" / "thinking-summaries" / "patch-extension.sh").read_text(encoding="utf-8") self.assertNotIn("mapfile", source) From cea31783d0dbe29f892283f78f089aa0837c0ff8 Mon Sep 17 00:00:00 2001 From: phase3dev <77866949+phase3dev@users.noreply.github.com> Date: Thu, 11 Jun 2026 05:09:24 -1000 Subject: [PATCH 4/7] docs(readme): date the fixes 2026-06-10; cap screenshots at max-width 100% - Bump all three workaround entries to [updated 2026-06-10]. - Render the screenshots via so they never overflow the container (GitHub already caps; this also covers the site). Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 14008d9..65a8b8e 100644 --- a/README.md +++ b/README.md @@ -6,25 +6,25 @@ Not affiliated with or endorsed by Anthropic. A future Claude Code update could ## Workarounds -1. **Empty thinking summaries (Opus 4.7 / 4.8)** [updated 2026-06-08]. +1. **Empty thinking summaries (Opus 4.7 / 4.8)** [updated 2026-06-10]. Thinking summaries render empty in the VS Code extension and headless `-p`/SDK paths, even with `showThinkingSummaries` enabled. Fix via the launcher (recommended), a one-line extension patch, or a local proxy. -> [details](#workaround-1-thinking-summaries) - ![A populated Thinking summary in the VS Code chat instead of an empty block](media/thinking.png) + A populated Thinking summary in the VS Code chat instead of an empty block -2. **Missing context-usage icon (1M context window)** [updated 2026-06-08]. +2. **Missing context-usage icon (1M context window)** [updated 2026-06-10]. The context-usage pie in the chat input is hidden until you have used more than 50% of the context window. With the 1M window that is about 500,000 tokens, so it is effectively never shown. Fix via the launcher (re-patches the webview on each launch), or a standalone patcher script. -> [details](#workaround-2-context-usage-icon) - ![The context-usage pie icon and tooltip showing at 19 percent used, below the old 50 percent threshold](media/context-icon.png) + The context-usage pie icon and tooltip showing at 19 percent used, below the old 50 percent threshold -3. **No markdown copy / export of chat** [added 2026-06-09]. +3. **No markdown copy / export of chat** [updated 2026-06-10]. The chat cannot copy a whole message or the whole conversation as Markdown, and has no transcript export. Fix via the launcher (adds copy controls, re-applied each launch), a standalone patcher, or a standalone session exporter CLI. -> [details](fixes/markdown-copy-export/README.md) - ![Per-message copy controls added to the VS Code chat](media/markdown.png) + Per-message copy controls added to the VS Code chat ## Quick start From 7495f822f4309aeeb89a3c384cc675e01460c9b2 Mon Sep 17 00:00:00 2001 From: phase3dev <77866949+phase3dev@users.noreply.github.com> Date: Thu, 11 Jun 2026 05:46:44 -1000 Subject: [PATCH 5/7] fix(context-icon): var-agnostic reversal of legacy bare markers Codex review on #8: a bundle an older var-agnostic launcher patched on a non-t/c build (the docs call out Z/U) carries a BARE /*ccwa-context-icon*/ marker on renamed guard vars. undo matched bare forms with literal t/c, so a Z/U bare form fell through to the broad >=101->>=50 fallback (bash), which reverted the gate but LEFT the marker; apply then exits early on the marker and commits the original 50% gate, hiding the icon. The Windows launcher and standalone patcher had the same gap (no fallback, so they left the bare form and warned). - bash + win.js + fix-context-icon.py: recognize the both-guards bare marker by shape (variable-name captures) and restore pristine, plus a final pass that strips any leftover bare marker so apply is never wedged by an unrecognized form. - Tests: a legacy Z/U bare marker upgrades to the metadata form on both launchers (enabled + disabled) and the standalone patcher. 113 tests pass. The ===0-removed bare form on non-t/c vars (if(U>=101)...}/*marker*/) is irrecoverable by construction - the original guard var was deleted with no metadata - and was never produced (bare markers were t/c-only; var-agnostic patching shipped with the metadata marker that records the removed var). The orphan-marker strip keeps even that hypothetical from wedging apply. Co-Authored-By: Claude Opus 4.8 (1M context) --- fixes/context-icon/fix-context-icon.py | 17 +++++++++++++---- launcher/claudemax | 14 +++++++++----- launcher/claudemax.win.js | 21 ++++++++++++++++++--- tests/test_reconcile.py | 25 +++++++++++++++++++++++++ tests/test_regressions.py | 18 ++++++++++++++++++ 5 files changed, 83 insertions(+), 12 deletions(-) diff --git a/fixes/context-icon/fix-context-icon.py b/fixes/context-icon/fix-context-icon.py index a72ad1c..1520489 100755 --- a/fixes/context-icon/fix-context-icon.py +++ b/fixes/context-icon/fix-context-icon.py @@ -64,12 +64,20 @@ MARKED_RE = re.compile( rf"if\(({IDENT})>=101\)return null\}}/\*ccwa-context-icon:({IDENT}):\1\*/" ) +# Legacy bare (metadata-less) marker on arbitrary guard names: an older +# var-agnostic write could leave the both-guards >=101 form + bare marker on a +# non-t/c build (e.g. Z/U). Recognize it by shape, not the fixed t/c. +LEGACY_NEW_RE = re.compile( + rf"if\(({IDENT})===0\)return null;if\(({IDENT})>=101\)return null\}}/\*ccwa-context-icon\*/" +) +# Strip any leftover bare marker so patch_file (which only re-applies on the +# pristine guard pair) is never wedged by an unrecognized bare-marked form. +ORPHAN_MARKER_RE = re.compile(r"\)return null\}/\*ccwa-context-icon\*/") OLD = "if(t===0)return null;if(c>=50)return null}" NEW_BARE = "if(c>=101)return null}" NEW_LEGACY = NEW_BARE + "/*ccwa-context-icon*/" NEW = "if(c>=101)return null}/*ccwa-context-icon:t:c*/" LEGACY_BARE = "if(t===0)return null;if(c>=101)return null}" -LEGACY_NEW = LEGACY_BARE + "/*ccwa-context-icon*/" BACKUP_SUFFIX = ".bak-context-icon" @@ -86,12 +94,13 @@ def marked_guard(first_var, remaining_var): def undo_known_patches(data): data = MARKED_RE.sub(lambda m: old_guard(m.group(2), m.group(1)), data) - return ( - data.replace(LEGACY_NEW, OLD) - .replace(NEW_LEGACY, OLD) + data = LEGACY_NEW_RE.sub(lambda m: old_guard(m.group(1), m.group(2)), data) + data = ( + data.replace(NEW_LEGACY, OLD) .replace(LEGACY_BARE, OLD) .replace(NEW_BARE, OLD) ) + return ORPHAN_MARKER_RE.sub(")return null}", data) DISCOVERY_GLOBS = [ os.path.expanduser("~/.vscode/extensions/anthropic.claude-code-*/webview/index.js"), diff --git a/launcher/claudemax b/launcher/claudemax index 50698da..9b76f2b 100755 --- a/launcher/claudemax +++ b/launcher/claudemax @@ -310,18 +310,22 @@ _cc_apply_context_icon() { _cc_undo_context_icon() { # Revert our edit to the pristine upstream form. Recognized fingerprints are: - # the current metadata-marked form, and legacy marked/bare forms that older - # versions wrote with fixed t/c names. The marked substitutions run first - # because bare strings are prefixes of marked strings. + # the current metadata-marked form; the legacy bare (metadata-less) marker on + # arbitrary guard names (older var-agnostic write); and legacy bare/unmarked + # forms that older t/c-only versions wrote. Marked substitutions run first + # because bare strings are prefixes of marked strings; a final pass strips any + # leftover bare marker so apply (which exits early on ANY marker) is never + # wedged by an unrecognized form. local f="$1" tmp grep -qF '>=101)return null}' "$f" 2>/dev/null || return 0 # nothing of ours tmp="${f}.ccundo.$$" if sed -e 's#if(\([A-Za-z_$][A-Za-z0-9_$]*\)>=101)return null}/\*ccwa-context-icon:\([A-Za-z_$][A-Za-z0-9_$]*\):\1\*/#if(\2===0)return null;if(\1>=50)return null}#g' \ - -e 's#if(t===0)return null;if(c>=101)return null}/\*ccwa-context-icon\*/#if(t===0)return null;if(c>=50)return null}#g' \ + -e 's#if(\([A-Za-z_$][A-Za-z0-9_$]*\)===0)return null;if(\([A-Za-z_$][A-Za-z0-9_$]*\)>=101)return null}/\*ccwa-context-icon\*/#if(\1===0)return null;if(\2>=50)return null}#g' \ -e 's#if(c>=101)return null}/\*ccwa-context-icon\*/#if(t===0)return null;if(c>=50)return null}#g' \ -e 's#if(t===0)return null;if(c>=101)return null}#if(t===0)return null;if(c>=50)return null}#g' \ -e 's#if(c>=101)return null}#if(t===0)return null;if(c>=50)return null}#g' \ - -e 's#>=101)return null}#>=50)return null}#g' "$f" > "$tmp" 2>/dev/null \ + -e 's#>=101)return null}#>=50)return null}#g' \ + -e 's#)return null}/\*ccwa-context-icon\*/#)return null}#g' "$f" > "$tmp" 2>/dev/null \ && [ -s "$tmp" ]; then cat "$tmp" > "$f" 2>/dev/null || true fi diff --git a/launcher/claudemax.win.js b/launcher/claudemax.win.js index 6789e41..deec5a1 100644 --- a/launcher/claudemax.win.js +++ b/launcher/claudemax.win.js @@ -264,7 +264,17 @@ const ICON_BARE = "if(c>=101)return null}"; const ICON_NEW = "if(c>=101)return null}/*ccwa-context-icon:t:c*/"; const ICON_LEGACY_NEW_CURRENT = ICON_BARE + ICON_LEGACY_MARKER; const ICON_LEGACY_BARE = "if(t===0)return null;if(c>=101)return null}"; -const ICON_LEGACY_NEW = ICON_LEGACY_BARE + ICON_LEGACY_MARKER; +// Legacy bare (metadata-less) marker on arbitrary guard names: an older +// var-agnostic write could leave the both-guards >=101 form + bare marker on a +// non-t/c build (e.g. Z/U). Match it by shape, not the fixed t/c. +const ICON_LEGACY_NEW_RE = new RegExp( + "if\\((" + + ICON_IDENT + + ")===0\\)return null;if\\((" + + ICON_IDENT + + ")>=101\\)return null\\}/\\*ccwa-context-icon\\*/", + "g" +); function iconOld(firstVar, remainingVar) { return `if(${firstVar}===0)return null;if(${remainingVar}>=50)return null}`; @@ -309,10 +319,15 @@ function undoContextIcon(data) { .replace(ICON_MARKED_RE, (_, remainingVar, firstVar) => iconOld(firstVar, remainingVar) ) - .split(ICON_LEGACY_NEW).join(ICON_OLD) + .replace(ICON_LEGACY_NEW_RE, (_, firstVar, remainingVar) => + iconOld(firstVar, remainingVar) + ) .split(ICON_LEGACY_NEW_CURRENT).join(ICON_OLD) .split(ICON_LEGACY_BARE).join(ICON_OLD) - .split(ICON_BARE).join(ICON_OLD); + .split(ICON_BARE).join(ICON_OLD) + // Strip any leftover bare marker so apply is never wedged by an unrecognized + // form (parity with the bash launcher's final undo pass). + .replace(/\)return null\}\/\*ccwa-context-icon\*\//g, ")return null}"); } function contextIconEnabled() { diff --git a/tests/test_reconcile.py b/tests/test_reconcile.py index 888f0ea..ce4e4b2 100644 --- a/tests/test_reconcile.py +++ b/tests/test_reconcile.py @@ -33,6 +33,9 @@ BARE101 = "if(t===0)return null;if(c>=101)return null}" ALT_OLD = "if(Z===0)return null;if(U>=50)return null}" ALT_MARKED = "if(U>=101)return null}/*ccwa-context-icon:Z:U*/" +# A var-agnostic older launcher could write the BARE (metadata-less) marker on a +# non-t/c build, e.g. Z/U. Undo must recognize it by shape, not the fixed t/c. +ALT_LEGACY_MARKED = "if(Z===0)return null;if(U>=101)return null}" + LEGACY_MARKER BAK = ".bak-cc-workarounds" MD_OPEN = "/* cc-md-copy v1 */" MD_CLOSE = "/* /cc-md-copy v1 */" @@ -184,6 +187,28 @@ def test_legacy_current_marked_patch_is_upgraded_to_metadata_marker(self): self.assertEqual(idx.read_text(encoding="utf-8"), f"before {MARKED} after") self.assertNotIn("anchor not found", res.stderr) + def test_legacy_bare_marked_patch_with_renamed_vars_is_upgraded(self): + # A bare (metadata-less) marker on a non-t/c build (Z/U). Undo must match + # the guard pair by shape and strip the marker so apply re-patches; the + # t/c-only fallback otherwise leaves the marker, and apply then exits early + # on it, committing the original >=50 gate (icon stays hidden). + with tempfile.TemporaryDirectory() as td: + home = pathlib.Path(td) + idx = make_extension(home, f"before {ALT_LEGACY_MARKED} after") + res = self._run(td, home) + self.assertEqual(res.returncode, 0, res.stderr) + self.assertEqual(idx.read_text(encoding="utf-8"), f"before {ALT_MARKED} after") + self.assertNotIn("anchor not found", res.stderr) + + def test_legacy_bare_marked_patch_with_renamed_vars_reverts_when_disabled(self): + with tempfile.TemporaryDirectory() as td: + home = pathlib.Path(td) + idx = make_extension(home, f"before {ALT_LEGACY_MARKED} after") + res = self._run(td, home, env_extra={"CC_PATCH_CONTEXT_ICON": "0"}) + self.assertEqual(res.returncode, 0, res.stderr) + self.assertEqual(idx.read_text(encoding="utf-8"), f"before {ALT_OLD} after") + self.assertNotIn("anchor not found", res.stderr) + def test_legacy_bare_patch_is_reverted_when_feature_disabled(self): # Migration-table promise: disabling the fix reverts our edit. A legacy # bare patch must revert to pristine just like a marked one does. diff --git a/tests/test_regressions.py b/tests/test_regressions.py index 6aecd98..7dac8ec 100644 --- a/tests/test_regressions.py +++ b/tests/test_regressions.py @@ -16,6 +16,7 @@ NEW_ICON = "if(c>=101)return null}/*ccwa-context-icon:t:c*/" ALT_OLD_ICON = "if(Z===0)return null;if(U>=50)return null}" ALT_NEW_ICON = "if(U>=101)return null}/*ccwa-context-icon:Z:U*/" +ALT_LEGACY_MARKED_ICON = "if(Z===0)return null;if(U>=101)return null}/*ccwa-context-icon*/" def run(cmd, *, env=None, cwd=REPO, timeout=10): @@ -389,6 +390,23 @@ def test_fix_context_icon_patches_renamed_minified_guard_vars(self): self.assertEqual(backup.read_text(encoding="utf-8"), f"before {ALT_OLD_ICON} after") self.assertEqual(mod.patch_file(str(target)), "already-patched") + def test_fix_context_icon_upgrades_renamed_bare_marked_patch(self): + # A bundle left by an older var-agnostic patcher carries a BARE + # (metadata-less) marker on non-t/c guard vars. undo_known_patches must + # recognize it by shape, not the fixed t/c, so patch_file re-applies and + # upgrades it to the metadata marker rather than reporting gate-not-found. + spec = importlib.util.spec_from_file_location( + "fix_context_icon", REPO / "fixes" / "context-icon" / "fix-context-icon.py" + ) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + + with tempfile.TemporaryDirectory() as td: + target = pathlib.Path(td) / "index.js" + target.write_text(f"before {ALT_LEGACY_MARKED_ICON} after", encoding="utf-8") + self.assertEqual(mod.patch_file(str(target)), "PATCHED") + self.assertEqual(target.read_text(encoding="utf-8"), f"before {ALT_NEW_ICON} after") + def test_patch_extension_avoids_bash4_mapfile(self): source = (REPO / "fixes" / "thinking-summaries" / "patch-extension.sh").read_text(encoding="utf-8") self.assertNotIn("mapfile", source) From 9a4545d4fce93a602fa1f4600b0ad1916368c0c0 Mon Sep 17 00:00:00 2001 From: phase3dev <77866949+phase3dev@users.noreply.github.com> Date: Thu, 11 Jun 2026 06:14:17 -1000 Subject: [PATCH 6/7] fix(context-icon): heal already-wedged bundle; drop over-broad bash undo Codex audit follow-up to 7495f82. Two bash-only gaps vs the Windows launcher and fix-context-icon.py, both of which already handled these: P1 - already-wedged state not recovered. _cc_undo_context_icon returned early unless it saw `>=101)return null}`, so a file an older buggy undo left as `if(Z===0)return null;if(U>=50)return null}/*ccwa-context-icon*/` (gate already reverted to >=50, bare marker still appended) skipped the orphan-marker strip. apply then early-exits on the leftover marker and the icon stays hidden. The early-out now also fires on a leftover marker, so the strip runs and heals it (pristine -> re-patch). As a side effect the emergency backup now captures the true pristine form, not the wedged intermediate. P2 - over-broad unowned rewrite. The generic `>=101)return null}` -> `>=50)return null}` fallback rewrote upstream code that merely resembled a patched value (e.g. an unowned `if(U>=101)return null}`), violating the ownership invariant. Removed it; every form we actually write is covered by the scoped substitutions above, so nothing real is lost. Tests: add the Z/U `>=50 + bare marker` wedged-heal regression (enabled and disabled) and an unowned `>=101` no-rewrite guard, across bash, Windows, and the standalone patcher. 113 -> 121 tests, all green. Co-Authored-By: Claude Opus 4.8 (1M context) --- launcher/claudemax | 15 +++++++++++--- tests/test_reconcile.py | 43 +++++++++++++++++++++++++++++++++++++++ tests/test_regressions.py | 38 ++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/launcher/claudemax b/launcher/claudemax index 9b76f2b..285d2f4 100755 --- a/launcher/claudemax +++ b/launcher/claudemax @@ -315,16 +315,25 @@ _cc_undo_context_icon() { # forms that older t/c-only versions wrote. Marked substitutions run first # because bare strings are prefixes of marked strings; a final pass strips any # leftover bare marker so apply (which exits early on ANY marker) is never - # wedged by an unrecognized form. + # wedged by an unrecognized form. We deliberately do NOT do a generic + # >=101->=50 rewrite: a bare >=101 guard with no marker is not necessarily ours, + # and rewriting it would corrupt upstream code that merely resembles a patched + # value (the ownership invariant above). Every form we actually write is covered + # by the scoped substitutions below. local f="$1" tmp - grep -qF '>=101)return null}' "$f" 2>/dev/null || return 0 # nothing of ours + # Nothing of ours: no >=101 guard AND no leftover marker. The marker check is + # load-bearing - a file an older buggy undo left wedged (gate already reverted to + # >=50 but the bare marker still appended) has no >=101, yet the orphan strip + # below must still run or apply stays wedged on the surviving marker. + grep -qF '>=101)return null}' "$f" 2>/dev/null \ + || grep -qF '/*ccwa-context-icon' "$f" 2>/dev/null \ + || return 0 tmp="${f}.ccundo.$$" if sed -e 's#if(\([A-Za-z_$][A-Za-z0-9_$]*\)>=101)return null}/\*ccwa-context-icon:\([A-Za-z_$][A-Za-z0-9_$]*\):\1\*/#if(\2===0)return null;if(\1>=50)return null}#g' \ -e 's#if(\([A-Za-z_$][A-Za-z0-9_$]*\)===0)return null;if(\([A-Za-z_$][A-Za-z0-9_$]*\)>=101)return null}/\*ccwa-context-icon\*/#if(\1===0)return null;if(\2>=50)return null}#g' \ -e 's#if(c>=101)return null}/\*ccwa-context-icon\*/#if(t===0)return null;if(c>=50)return null}#g' \ -e 's#if(t===0)return null;if(c>=101)return null}#if(t===0)return null;if(c>=50)return null}#g' \ -e 's#if(c>=101)return null}#if(t===0)return null;if(c>=50)return null}#g' \ - -e 's#>=101)return null}#>=50)return null}#g' \ -e 's#)return null}/\*ccwa-context-icon\*/#)return null}#g' "$f" > "$tmp" 2>/dev/null \ && [ -s "$tmp" ]; then cat "$tmp" > "$f" 2>/dev/null || true diff --git a/tests/test_reconcile.py b/tests/test_reconcile.py index ce4e4b2..bcb0c71 100644 --- a/tests/test_reconcile.py +++ b/tests/test_reconcile.py @@ -36,6 +36,15 @@ # A var-agnostic older launcher could write the BARE (metadata-less) marker on a # non-t/c build, e.g. Z/U. Undo must recognize it by shape, not the fixed t/c. ALT_LEGACY_MARKED = "if(Z===0)return null;if(U>=101)return null}" + LEGACY_MARKER +# The already-wedged state an OLD buggy undo could commit: the pristine >=50 gate +# restored but the bare marker left behind (renamed Z/U vars). undo must strip the +# orphan marker so apply re-patches; otherwise apply early-exits on the marker and +# the icon stays hidden. +ALT_WEDGED = ALT_OLD + LEGACY_MARKER +# Upstream code that merely RESEMBLES a patched value: a bare >=101 guard on a +# renamed var, with NO marker and NO ===0 prefix. We never write this, so undo must +# leave it untouched (ownership invariant), never rewrite it to >=50. +UNOWNED_BARE101 = "if(U>=101)return null}" BAK = ".bak-cc-workarounds" MD_OPEN = "/* cc-md-copy v1 */" MD_CLOSE = "/* /cc-md-copy v1 */" @@ -209,6 +218,40 @@ def test_legacy_bare_marked_patch_with_renamed_vars_reverts_when_disabled(self): self.assertEqual(idx.read_text(encoding="utf-8"), f"before {ALT_OLD} after") self.assertNotIn("anchor not found", res.stderr) + def test_already_wedged_bare_marker_is_healed_and_repatched(self): + # An OLD buggy undo could leave the file already wedged: the >=50 gate + # restored but the bare marker still appended (renamed Z/U vars). undo's + # early-out must not skip the orphan-marker strip, or apply early-exits on + # the leftover marker (icon stays hidden). The launcher must heal this on + # the next run: strip the orphan marker, then re-patch to the marked form. + with tempfile.TemporaryDirectory() as td: + home = pathlib.Path(td) + idx = make_extension(home, f"before {ALT_WEDGED} after") + res = self._run(td, home) + self.assertEqual(res.returncode, 0, res.stderr) + self.assertEqual(idx.read_text(encoding="utf-8"), f"before {ALT_MARKED} after") + + def test_already_wedged_bare_marker_reverts_to_pristine_when_disabled(self): + with tempfile.TemporaryDirectory() as td: + home = pathlib.Path(td) + idx = make_extension(home, f"before {ALT_WEDGED} after") + res = self._run(td, home, env_extra={"CC_PATCH_CONTEXT_ICON": "0"}) + self.assertEqual(res.returncode, 0, res.stderr) + self.assertEqual(idx.read_text(encoding="utf-8"), f"before {ALT_OLD} after") + + def test_unowned_bare_101_guard_is_left_untouched(self): + # Ownership invariant: a bare >=101 guard with no marker and no ===0 prefix + # is not ours (we always write a marker). undo must NOT rewrite it to >=50 - + # that corrupts upstream code that merely resembles a patched value. + with tempfile.TemporaryDirectory() as td: + home = pathlib.Path(td) + original = f"before {UNOWNED_BARE101} after" + idx = make_extension(home, original) + res = self._run(td, home) + self.assertEqual(res.returncode, 0, res.stderr) + self.assertEqual(idx.read_text(encoding="utf-8"), original) + self.assertNotIn(">=50)return null}", idx.read_text(encoding="utf-8")) + def test_legacy_bare_patch_is_reverted_when_feature_disabled(self): # Migration-table promise: disabling the fix reverts our edit. A legacy # bare patch must revert to pristine just like a marked one does. diff --git a/tests/test_regressions.py b/tests/test_regressions.py index 7dac8ec..65c9c0b 100644 --- a/tests/test_regressions.py +++ b/tests/test_regressions.py @@ -17,6 +17,12 @@ ALT_OLD_ICON = "if(Z===0)return null;if(U>=50)return null}" ALT_NEW_ICON = "if(U>=101)return null}/*ccwa-context-icon:Z:U*/" ALT_LEGACY_MARKED_ICON = "if(Z===0)return null;if(U>=101)return null}/*ccwa-context-icon*/" +# Already-wedged state: pristine >=50 gate restored but the bare marker left behind +# (renamed Z/U vars). undo must strip the orphan marker so patch_file re-applies. +ALT_WEDGED_ICON = "if(Z===0)return null;if(U>=50)return null}/*ccwa-context-icon*/" +# Upstream code that resembles a patched value: bare >=101, no marker, no ===0 +# prefix. Not ours - undo must leave it untouched (never rewrite to >=50). +UNOWNED_BARE101_ICON = "if(U>=101)return null}" def run(cmd, *, env=None, cwd=REPO, timeout=10): @@ -407,6 +413,38 @@ def test_fix_context_icon_upgrades_renamed_bare_marked_patch(self): self.assertEqual(mod.patch_file(str(target)), "PATCHED") self.assertEqual(target.read_text(encoding="utf-8"), f"before {ALT_NEW_ICON} after") + def test_fix_context_icon_heals_already_wedged_bare_marker(self): + # An older buggy undo path could leave the file wedged: the >=50 gate + # restored but the bare marker still appended (renamed Z/U vars). + # undo_known_patches must strip the orphan marker so patch_file re-applies + # rather than treating the leftover marker as already-patched. + spec = importlib.util.spec_from_file_location( + "fix_context_icon", REPO / "fixes" / "context-icon" / "fix-context-icon.py" + ) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + with tempfile.TemporaryDirectory() as td: + target = pathlib.Path(td) / "index.js" + target.write_text(f"before {ALT_WEDGED_ICON} after", encoding="utf-8") + self.assertEqual(mod.patch_file(str(target)), "PATCHED") + self.assertEqual(target.read_text(encoding="utf-8"), f"before {ALT_NEW_ICON} after") + + def test_fix_context_icon_leaves_unowned_bare_101_untouched(self): + # Ownership invariant: a bare >=101 guard with no marker and no ===0 prefix + # is not ours. undo must not rewrite it to >=50; patch_file reports the + # anchor as missing and leaves the file unchanged. + spec = importlib.util.spec_from_file_location( + "fix_context_icon", REPO / "fixes" / "context-icon" / "fix-context-icon.py" + ) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + with tempfile.TemporaryDirectory() as td: + target = pathlib.Path(td) / "index.js" + original = f"before {UNOWNED_BARE101_ICON} after" + target.write_text(original, encoding="utf-8") + self.assertIn("gate-not-found", mod.patch_file(str(target))) + self.assertEqual(target.read_text(encoding="utf-8"), original) + def test_patch_extension_avoids_bash4_mapfile(self): source = (REPO / "fixes" / "thinking-summaries" / "patch-extension.sh").read_text(encoding="utf-8") self.assertNotIn("mapfile", source) From b5f6bdf342009dc4f7fd39335fae0385b950cc64 Mon Sep 17 00:00:00 2001 From: phase3dev <77866949+phase3dev@users.noreply.github.com> Date: Thu, 11 Jun 2026 06:43:10 -1000 Subject: [PATCH 7/7] docs(readme): update context-icon screenshot Replace the context-usage screenshot and correct the alt text to match: the new capture shows the icon and tooltip at 51 percent context used (49 percent remaining until auto-compact). Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 2 +- media/context-icon.png | Bin 20304 -> 30462 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 65a8b8e..41318aa 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Not affiliated with or endorsed by Anthropic. A future Claude Code update could The context-usage pie in the chat input is hidden until you have used more than 50% of the context window. With the 1M window that is about 500,000 tokens, so it is effectively never shown. Fix via the launcher (re-patches the webview on each launch), or a standalone patcher script. -> [details](#workaround-2-context-usage-icon) - The context-usage pie icon and tooltip showing at 19 percent used, below the old 50 percent threshold + The context-usage icon and tooltip in the VS Code chat input, showing 51 percent context used and 49 percent remaining until auto-compact 3. **No markdown copy / export of chat** [updated 2026-06-10]. The chat cannot copy a whole message or the whole conversation as Markdown, and diff --git a/media/context-icon.png b/media/context-icon.png index 3cc6398d85c7f7ff0f168e77e224b76a268c6145..6901e4c7ed81ee62d651b942e9d6a6062ad4289d 100644 GIT binary patch literal 30462 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 z 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∨9CgWckz80Rj{HyQ2?lVFe${7G0*F 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