visionclaw 0.1.207-beta.10 → 0.1.207-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/obs/mini-app/status.ts"],"names":[],"mappings":"AAAA,wBAAgB,gBAAgB,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/obs/mini-app/status.ts"],"names":[],"mappings":"AAAA,wBAAgB,gBAAgB,IAAI,MAAM,CAsyBzC"}
|
|
@@ -96,6 +96,60 @@ export function renderStatusHtml() {
|
|
|
96
96
|
max-width: 78%;
|
|
97
97
|
word-break: break-all;
|
|
98
98
|
}
|
|
99
|
+
/* The copy icon eats ~19px of the value column, which is enough to wrap an
|
|
100
|
+
address that used to fit on one line. Buy that width back. A class, not
|
|
101
|
+
:has(), so this does not depend on selector support in older WebViews. */
|
|
102
|
+
.card-row .value.wide.copyable {
|
|
103
|
+
max-width: 84%;
|
|
104
|
+
}
|
|
105
|
+
/* Telegram's mobile WebView blocks long-press text selection, so a
|
|
106
|
+
plain-text value cannot be copied at all. A value that exists to be
|
|
107
|
+
copied (the email) renders as a button that writes to the clipboard.
|
|
108
|
+
It must look like the surrounding text, not like chrome. */
|
|
109
|
+
.card-row .value .copy-btn {
|
|
110
|
+
display: inline-flex;
|
|
111
|
+
/* Top, not baseline: when the address wraps, the icon should sit beside
|
|
112
|
+
the first line rather than drift down with the last one. */
|
|
113
|
+
align-items: flex-start;
|
|
114
|
+
gap: 6px;
|
|
115
|
+
background: none;
|
|
116
|
+
border: none;
|
|
117
|
+
padding: 0;
|
|
118
|
+
margin: 0;
|
|
119
|
+
font: inherit;
|
|
120
|
+
color: inherit;
|
|
121
|
+
text-align: right;
|
|
122
|
+
cursor: pointer;
|
|
123
|
+
/* Still allow native selection wherever the WebView permits it. */
|
|
124
|
+
-webkit-user-select: text;
|
|
125
|
+
user-select: text;
|
|
126
|
+
}
|
|
127
|
+
/* The address breaks inside its own column, so the icon never lands
|
|
128
|
+
mid-token and never strands a single character on a second line. */
|
|
129
|
+
.card-row .value .copy-text {
|
|
130
|
+
min-width: 0;
|
|
131
|
+
word-break: break-all;
|
|
132
|
+
}
|
|
133
|
+
.card-row .value .copy-icon {
|
|
134
|
+
font-size: 13px;
|
|
135
|
+
opacity: 0.65;
|
|
136
|
+
flex-shrink: 0;
|
|
137
|
+
}
|
|
138
|
+
.card-row .value .copy-btn.copied { color: #2e7d32; }
|
|
139
|
+
.card-row .value .copy-btn.copied .copy-icon { opacity: 1; }
|
|
140
|
+
/* The copy result is conveyed by an aria-hidden glyph, so the live region
|
|
141
|
+
below carries it for screen readers. Kept in the a11y tree, off-screen. */
|
|
142
|
+
.sr-only {
|
|
143
|
+
position: absolute;
|
|
144
|
+
width: 1px;
|
|
145
|
+
height: 1px;
|
|
146
|
+
margin: -1px;
|
|
147
|
+
padding: 0;
|
|
148
|
+
overflow: hidden;
|
|
149
|
+
clip: rect(0, 0, 0, 0);
|
|
150
|
+
white-space: nowrap;
|
|
151
|
+
border: 0;
|
|
152
|
+
}
|
|
99
153
|
.badge {
|
|
100
154
|
display: inline-flex;
|
|
101
155
|
align-items: center;
|
|
@@ -346,12 +400,16 @@ export function renderStatusHtml() {
|
|
|
346
400
|
.step-icon.action { background: #0d47a1; }
|
|
347
401
|
.step-icon.result { background: #4a148c; }
|
|
348
402
|
.task-elapsed { background: #e65100; color: #ffe0b2; }
|
|
403
|
+
.card-row .value .copy-btn.copied { color: #81c784; }
|
|
349
404
|
.progress-shimmer { background: linear-gradient(90deg, #0d47a1 25%, #42a5f5 50%, #0d47a1 75%); background-size: 200% 100%; }
|
|
350
405
|
}
|
|
351
406
|
</style>
|
|
352
407
|
</head>
|
|
353
408
|
<body>
|
|
354
409
|
<div id="app"><div class="loading">Loading status…</div></div>
|
|
410
|
+
<!-- Outside #app on purpose: render() replaces #app.innerHTML every 5s, which
|
|
411
|
+
would destroy the live region (and any pending announcement) mid-poll. -->
|
|
412
|
+
<div id="copy-status" class="sr-only" role="status" aria-live="polite"></div>
|
|
355
413
|
<script>
|
|
356
414
|
const tg = window.Telegram?.WebApp;
|
|
357
415
|
if (tg) {
|
|
@@ -505,7 +563,7 @@ export function renderStatusHtml() {
|
|
|
505
563
|
'<div class="card">' +
|
|
506
564
|
'<div class="card-title">System</div>' +
|
|
507
565
|
row('Status', '<span class="badge ' + statusClass + '">' + statusIcon + ' ' + statusLabel + '</span>') +
|
|
508
|
-
|
|
566
|
+
copyRow('Email', d.agentEmail, 'wide') +
|
|
509
567
|
row('Uptime', esc(d.uptime || '—')) +
|
|
510
568
|
row('Heartbeat', esc(d.heartbeat || '—')) +
|
|
511
569
|
row('Model', esc(d.model || '—')) +
|
|
@@ -536,11 +594,33 @@ export function renderStatusHtml() {
|
|
|
536
594
|
'">' + value + '</span></div>';
|
|
537
595
|
}
|
|
538
596
|
|
|
597
|
+
// A row whose value is meant to be copied. Falls back to a plain row when
|
|
598
|
+
// there is nothing to copy, so the em dash never looks tappable.
|
|
599
|
+
function copyRow(label, value, valueClass) {
|
|
600
|
+
if (!value) return row(label, '—', valueClass);
|
|
601
|
+
return row(
|
|
602
|
+
label,
|
|
603
|
+
'<button type="button" class="copy-btn" data-copy="' + escAttr(value) +
|
|
604
|
+
'" data-copy-label="' + escAttr(label) +
|
|
605
|
+
'" aria-label="Copy ' + escAttr(label) + '">' +
|
|
606
|
+
'<span class="copy-text">' + esc(value) + '</span>' +
|
|
607
|
+
'<span class="copy-icon" aria-hidden="true">📋</span>' +
|
|
608
|
+
'</button>',
|
|
609
|
+
(valueClass ? valueClass + ' ' : '') + 'copyable',
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
|
|
539
613
|
function esc(s) {
|
|
540
614
|
if (!s) return '';
|
|
541
615
|
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
542
616
|
}
|
|
543
617
|
|
|
618
|
+
// Attribute values additionally need quotes escaped, or a crafted value
|
|
619
|
+
// could close the attribute and inject markup.
|
|
620
|
+
function escAttr(s) {
|
|
621
|
+
return esc(s).replace(/"/g,'"').replace(/'/g,''');
|
|
622
|
+
}
|
|
623
|
+
|
|
544
624
|
function actionTimestamp(action) {
|
|
545
625
|
return typeof action === 'object' && action ? Number(action.timestamp) || 0 : 0;
|
|
546
626
|
}
|
|
@@ -644,6 +724,81 @@ export function renderStatusHtml() {
|
|
|
644
724
|
return { icon: isAction ? '⚙️' : '📋', label: isResult ? humanName + ' — done' : humanName, detail: detail, type: isResult ? 'result' : 'action' };
|
|
645
725
|
}
|
|
646
726
|
|
|
727
|
+
async function writeClipboard(text) {
|
|
728
|
+
try {
|
|
729
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
730
|
+
await navigator.clipboard.writeText(text);
|
|
731
|
+
return true;
|
|
732
|
+
}
|
|
733
|
+
} catch (e) {
|
|
734
|
+
// Fall through: the async API is unavailable on insecure origins and
|
|
735
|
+
// rejects without a user-gesture in some WebViews.
|
|
736
|
+
}
|
|
737
|
+
// Held outside the try: execCommand throwing is a normal failure mode in
|
|
738
|
+
// the very WebViews this fallback exists for, and the node must still be
|
|
739
|
+
// detached on that path or every tap leaks one.
|
|
740
|
+
let ta = null;
|
|
741
|
+
try {
|
|
742
|
+
ta = document.createElement('textarea');
|
|
743
|
+
ta.value = text;
|
|
744
|
+
ta.setAttribute('readonly', '');
|
|
745
|
+
ta.style.position = 'fixed';
|
|
746
|
+
ta.style.opacity = '0';
|
|
747
|
+
document.body.appendChild(ta);
|
|
748
|
+
ta.select();
|
|
749
|
+
ta.setSelectionRange(0, text.length);
|
|
750
|
+
return document.execCommand('copy');
|
|
751
|
+
} catch (e) {
|
|
752
|
+
return false;
|
|
753
|
+
} finally {
|
|
754
|
+
if (ta && ta.isConnected) ta.remove();
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// Lives outside #app, so the 5s re-render cannot drop it. Writing the same
|
|
759
|
+
// string twice would not re-announce, so blank it first.
|
|
760
|
+
function announceCopy(message) {
|
|
761
|
+
const region = document.getElementById('copy-status');
|
|
762
|
+
if (!region) return;
|
|
763
|
+
region.textContent = '';
|
|
764
|
+
if (message) region.textContent = message;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// render() replaces #app.innerHTML every 5s, which would destroy a listener
|
|
768
|
+
// bound to the button itself — so delegate from the container instead.
|
|
769
|
+
let copyResetTimer = null;
|
|
770
|
+
document.getElementById('app').addEventListener('click', async (event) => {
|
|
771
|
+
const btn = event.target.closest ? event.target.closest('.copy-btn') : null;
|
|
772
|
+
if (!btn) return;
|
|
773
|
+
const text = btn.getAttribute('data-copy') || '';
|
|
774
|
+
if (!text) return;
|
|
775
|
+
|
|
776
|
+
const ok = await writeClipboard(text);
|
|
777
|
+
const icon = btn.querySelector('.copy-icon');
|
|
778
|
+
btn.classList.toggle('copied', ok);
|
|
779
|
+
if (icon) icon.textContent = ok ? '✅' : '⚠️';
|
|
780
|
+
// The glyph is aria-hidden, so the outcome only reaches a screen reader
|
|
781
|
+
// through the live region.
|
|
782
|
+
const what = btn.getAttribute('data-copy-label') || 'Value';
|
|
783
|
+
announceCopy(ok ? what + ' copied to clipboard' : 'Could not copy ' + what);
|
|
784
|
+
try {
|
|
785
|
+
tg?.HapticFeedback?.notificationOccurred(ok ? 'success' : 'error');
|
|
786
|
+
} catch (e) {
|
|
787
|
+
// HapticFeedback is absent outside the Telegram client.
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Revert the affordance so a later copy still reads as feedback. The
|
|
791
|
+
// next poll re-renders the row anyway; this covers the gap until then.
|
|
792
|
+
if (copyResetTimer) clearTimeout(copyResetTimer);
|
|
793
|
+
copyResetTimer = setTimeout(() => {
|
|
794
|
+
btn.classList.remove('copied');
|
|
795
|
+
if (icon) icon.textContent = '📋';
|
|
796
|
+
// Clear the announcement too, so the stale outcome is not re-read and
|
|
797
|
+
// the next copy of the same value announces again.
|
|
798
|
+
announceCopy('');
|
|
799
|
+
}, 1500);
|
|
800
|
+
});
|
|
801
|
+
|
|
647
802
|
load();
|
|
648
803
|
setInterval(load, 5000);
|
|
649
804
|
</script>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/obs/mini-app/status.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,gBAAgB;IAC9B,OAAO
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/obs/mini-app/status.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,gBAAgB;IAC9B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAoyBD,CAAC;AACT,CAAC"}
|
package/dist-agent/bundle.cjs
CHANGED
|
@@ -15974,7 +15974,7 @@ ${DIM}${"\u2500".repeat(70)}${RESET}`);
|
|
|
15974
15974
|
|
|
15975
15975
|
// dist/utils/version-check.js
|
|
15976
15976
|
function isBundled() {
|
|
15977
|
-
const v16 = "0.1.207-beta.
|
|
15977
|
+
const v16 = "0.1.207-beta.11";
|
|
15978
15978
|
return typeof v16 === "string" && v16 !== "undefined";
|
|
15979
15979
|
}
|
|
15980
15980
|
function getPackageRoot() {
|
|
@@ -15991,7 +15991,7 @@ function getInstallationInfo() {
|
|
|
15991
15991
|
};
|
|
15992
15992
|
}
|
|
15993
15993
|
function getCurrentVersion() {
|
|
15994
|
-
const bundledVersion = "0.1.207-beta.
|
|
15994
|
+
const bundledVersion = "0.1.207-beta.11";
|
|
15995
15995
|
if (bundledVersion && bundledVersion !== "undefined") {
|
|
15996
15996
|
return bundledVersion;
|
|
15997
15997
|
}
|
|
@@ -977763,7 +977763,7 @@ var init_playwriter_extension = __esm({
|
|
|
977763
977763
|
|
|
977764
977764
|
// dist/utils/playwriter-relay.js
|
|
977765
977765
|
function isBundled2() {
|
|
977766
|
-
const v16 = "0.1.207-beta.
|
|
977766
|
+
const v16 = "0.1.207-beta.11";
|
|
977767
977767
|
return typeof v16 === "string" && v16 !== "undefined";
|
|
977768
977768
|
}
|
|
977769
977769
|
function resolveRelayEntryPath() {
|
|
@@ -977889,7 +977889,7 @@ function selectAndPruneCanonicalRelay(matches, expectedEntry) {
|
|
|
977889
977889
|
return canonical;
|
|
977890
977890
|
}
|
|
977891
977891
|
function pm2Start(entryPath, host, port) {
|
|
977892
|
-
const versionStr = "0.1.207-beta.
|
|
977892
|
+
const versionStr = "0.1.207-beta.11";
|
|
977893
977893
|
(0, import_node_child_process13.execFileSync)("pm2", ["start", entryPath, "--name", PM2_PROCESS_NAME, "--update-env"], {
|
|
977894
977894
|
encoding: "utf-8",
|
|
977895
977895
|
stdio: "pipe",
|
|
@@ -977915,7 +977915,7 @@ function pm2Delete() {
|
|
|
977915
977915
|
}
|
|
977916
977916
|
}
|
|
977917
977917
|
function pm2Restart(host, port) {
|
|
977918
|
-
const versionStr = "0.1.207-beta.
|
|
977918
|
+
const versionStr = "0.1.207-beta.11";
|
|
977919
977919
|
(0, import_node_child_process13.execFileSync)("pm2", ["restart", PM2_PROCESS_NAME, "--update-env"], {
|
|
977920
977920
|
encoding: "utf-8",
|
|
977921
977921
|
stdio: "pipe",
|
|
@@ -978384,7 +978384,7 @@ async function reconcilePlaywriterRelay(host = "127.0.0.1", port = 19988) {
|
|
|
978384
978384
|
pm2Restart(host, port);
|
|
978385
978385
|
return;
|
|
978386
978386
|
}
|
|
978387
|
-
const currentVersion = "0.1.207-beta.
|
|
978387
|
+
const currentVersion = "0.1.207-beta.11";
|
|
978388
978388
|
if (currentEnv.VISIONCLAW_VERSION && currentEnv.VISIONCLAW_VERSION !== currentVersion) {
|
|
978389
978389
|
if (!await ensureRelayPortAvailable(host, port, current, "recreate after a version change"))
|
|
978390
978390
|
return;
|
|
@@ -1422583,6 +1422583,60 @@ function renderStatusHtml() {
|
|
|
1422583
1422583
|
max-width: 78%;
|
|
1422584
1422584
|
word-break: break-all;
|
|
1422585
1422585
|
}
|
|
1422586
|
+
/* The copy icon eats ~19px of the value column, which is enough to wrap an
|
|
1422587
|
+
address that used to fit on one line. Buy that width back. A class, not
|
|
1422588
|
+
:has(), so this does not depend on selector support in older WebViews. */
|
|
1422589
|
+
.card-row .value.wide.copyable {
|
|
1422590
|
+
max-width: 84%;
|
|
1422591
|
+
}
|
|
1422592
|
+
/* Telegram's mobile WebView blocks long-press text selection, so a
|
|
1422593
|
+
plain-text value cannot be copied at all. A value that exists to be
|
|
1422594
|
+
copied (the email) renders as a button that writes to the clipboard.
|
|
1422595
|
+
It must look like the surrounding text, not like chrome. */
|
|
1422596
|
+
.card-row .value .copy-btn {
|
|
1422597
|
+
display: inline-flex;
|
|
1422598
|
+
/* Top, not baseline: when the address wraps, the icon should sit beside
|
|
1422599
|
+
the first line rather than drift down with the last one. */
|
|
1422600
|
+
align-items: flex-start;
|
|
1422601
|
+
gap: 6px;
|
|
1422602
|
+
background: none;
|
|
1422603
|
+
border: none;
|
|
1422604
|
+
padding: 0;
|
|
1422605
|
+
margin: 0;
|
|
1422606
|
+
font: inherit;
|
|
1422607
|
+
color: inherit;
|
|
1422608
|
+
text-align: right;
|
|
1422609
|
+
cursor: pointer;
|
|
1422610
|
+
/* Still allow native selection wherever the WebView permits it. */
|
|
1422611
|
+
-webkit-user-select: text;
|
|
1422612
|
+
user-select: text;
|
|
1422613
|
+
}
|
|
1422614
|
+
/* The address breaks inside its own column, so the icon never lands
|
|
1422615
|
+
mid-token and never strands a single character on a second line. */
|
|
1422616
|
+
.card-row .value .copy-text {
|
|
1422617
|
+
min-width: 0;
|
|
1422618
|
+
word-break: break-all;
|
|
1422619
|
+
}
|
|
1422620
|
+
.card-row .value .copy-icon {
|
|
1422621
|
+
font-size: 13px;
|
|
1422622
|
+
opacity: 0.65;
|
|
1422623
|
+
flex-shrink: 0;
|
|
1422624
|
+
}
|
|
1422625
|
+
.card-row .value .copy-btn.copied { color: #2e7d32; }
|
|
1422626
|
+
.card-row .value .copy-btn.copied .copy-icon { opacity: 1; }
|
|
1422627
|
+
/* The copy result is conveyed by an aria-hidden glyph, so the live region
|
|
1422628
|
+
below carries it for screen readers. Kept in the a11y tree, off-screen. */
|
|
1422629
|
+
.sr-only {
|
|
1422630
|
+
position: absolute;
|
|
1422631
|
+
width: 1px;
|
|
1422632
|
+
height: 1px;
|
|
1422633
|
+
margin: -1px;
|
|
1422634
|
+
padding: 0;
|
|
1422635
|
+
overflow: hidden;
|
|
1422636
|
+
clip: rect(0, 0, 0, 0);
|
|
1422637
|
+
white-space: nowrap;
|
|
1422638
|
+
border: 0;
|
|
1422639
|
+
}
|
|
1422586
1422640
|
.badge {
|
|
1422587
1422641
|
display: inline-flex;
|
|
1422588
1422642
|
align-items: center;
|
|
@@ -1422833,12 +1422887,16 @@ function renderStatusHtml() {
|
|
|
1422833
1422887
|
.step-icon.action { background: #0d47a1; }
|
|
1422834
1422888
|
.step-icon.result { background: #4a148c; }
|
|
1422835
1422889
|
.task-elapsed { background: #e65100; color: #ffe0b2; }
|
|
1422890
|
+
.card-row .value .copy-btn.copied { color: #81c784; }
|
|
1422836
1422891
|
.progress-shimmer { background: linear-gradient(90deg, #0d47a1 25%, #42a5f5 50%, #0d47a1 75%); background-size: 200% 100%; }
|
|
1422837
1422892
|
}
|
|
1422838
1422893
|
</style>
|
|
1422839
1422894
|
</head>
|
|
1422840
1422895
|
<body>
|
|
1422841
1422896
|
<div id="app"><div class="loading">Loading status\u2026</div></div>
|
|
1422897
|
+
<!-- Outside #app on purpose: render() replaces #app.innerHTML every 5s, which
|
|
1422898
|
+
would destroy the live region (and any pending announcement) mid-poll. -->
|
|
1422899
|
+
<div id="copy-status" class="sr-only" role="status" aria-live="polite"></div>
|
|
1422842
1422900
|
<script>
|
|
1422843
1422901
|
const tg = window.Telegram?.WebApp;
|
|
1422844
1422902
|
if (tg) {
|
|
@@ -1422992,7 +1423050,7 @@ function renderStatusHtml() {
|
|
|
1422992
1423050
|
'<div class="card">' +
|
|
1422993
1423051
|
'<div class="card-title">System</div>' +
|
|
1422994
1423052
|
row('Status', '<span class="badge ' + statusClass + '">' + statusIcon + ' ' + statusLabel + '</span>') +
|
|
1422995
|
-
|
|
1423053
|
+
copyRow('Email', d.agentEmail, 'wide') +
|
|
1422996
1423054
|
row('Uptime', esc(d.uptime || '\u2014')) +
|
|
1422997
1423055
|
row('Heartbeat', esc(d.heartbeat || '\u2014')) +
|
|
1422998
1423056
|
row('Model', esc(d.model || '\u2014')) +
|
|
@@ -1423023,11 +1423081,33 @@ function renderStatusHtml() {
|
|
|
1423023
1423081
|
'">' + value + '</span></div>';
|
|
1423024
1423082
|
}
|
|
1423025
1423083
|
|
|
1423084
|
+
// A row whose value is meant to be copied. Falls back to a plain row when
|
|
1423085
|
+
// there is nothing to copy, so the em dash never looks tappable.
|
|
1423086
|
+
function copyRow(label, value, valueClass) {
|
|
1423087
|
+
if (!value) return row(label, '\u2014', valueClass);
|
|
1423088
|
+
return row(
|
|
1423089
|
+
label,
|
|
1423090
|
+
'<button type="button" class="copy-btn" data-copy="' + escAttr(value) +
|
|
1423091
|
+
'" data-copy-label="' + escAttr(label) +
|
|
1423092
|
+
'" aria-label="Copy ' + escAttr(label) + '">' +
|
|
1423093
|
+
'<span class="copy-text">' + esc(value) + '</span>' +
|
|
1423094
|
+
'<span class="copy-icon" aria-hidden="true">\u{1F4CB}</span>' +
|
|
1423095
|
+
'</button>',
|
|
1423096
|
+
(valueClass ? valueClass + ' ' : '') + 'copyable',
|
|
1423097
|
+
);
|
|
1423098
|
+
}
|
|
1423099
|
+
|
|
1423026
1423100
|
function esc(s) {
|
|
1423027
1423101
|
if (!s) return '';
|
|
1423028
1423102
|
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
1423029
1423103
|
}
|
|
1423030
1423104
|
|
|
1423105
|
+
// Attribute values additionally need quotes escaped, or a crafted value
|
|
1423106
|
+
// could close the attribute and inject markup.
|
|
1423107
|
+
function escAttr(s) {
|
|
1423108
|
+
return esc(s).replace(/"/g,'"').replace(/'/g,''');
|
|
1423109
|
+
}
|
|
1423110
|
+
|
|
1423031
1423111
|
function actionTimestamp(action) {
|
|
1423032
1423112
|
return typeof action === 'object' && action ? Number(action.timestamp) || 0 : 0;
|
|
1423033
1423113
|
}
|
|
@@ -1423131,6 +1423211,81 @@ function renderStatusHtml() {
|
|
|
1423131
1423211
|
return { icon: isAction ? '\u2699\uFE0F' : '\u{1F4CB}', label: isResult ? humanName + ' \u2014 done' : humanName, detail: detail, type: isResult ? 'result' : 'action' };
|
|
1423132
1423212
|
}
|
|
1423133
1423213
|
|
|
1423214
|
+
async function writeClipboard(text) {
|
|
1423215
|
+
try {
|
|
1423216
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
1423217
|
+
await navigator.clipboard.writeText(text);
|
|
1423218
|
+
return true;
|
|
1423219
|
+
}
|
|
1423220
|
+
} catch (e) {
|
|
1423221
|
+
// Fall through: the async API is unavailable on insecure origins and
|
|
1423222
|
+
// rejects without a user-gesture in some WebViews.
|
|
1423223
|
+
}
|
|
1423224
|
+
// Held outside the try: execCommand throwing is a normal failure mode in
|
|
1423225
|
+
// the very WebViews this fallback exists for, and the node must still be
|
|
1423226
|
+
// detached on that path or every tap leaks one.
|
|
1423227
|
+
let ta = null;
|
|
1423228
|
+
try {
|
|
1423229
|
+
ta = document.createElement('textarea');
|
|
1423230
|
+
ta.value = text;
|
|
1423231
|
+
ta.setAttribute('readonly', '');
|
|
1423232
|
+
ta.style.position = 'fixed';
|
|
1423233
|
+
ta.style.opacity = '0';
|
|
1423234
|
+
document.body.appendChild(ta);
|
|
1423235
|
+
ta.select();
|
|
1423236
|
+
ta.setSelectionRange(0, text.length);
|
|
1423237
|
+
return document.execCommand('copy');
|
|
1423238
|
+
} catch (e) {
|
|
1423239
|
+
return false;
|
|
1423240
|
+
} finally {
|
|
1423241
|
+
if (ta && ta.isConnected) ta.remove();
|
|
1423242
|
+
}
|
|
1423243
|
+
}
|
|
1423244
|
+
|
|
1423245
|
+
// Lives outside #app, so the 5s re-render cannot drop it. Writing the same
|
|
1423246
|
+
// string twice would not re-announce, so blank it first.
|
|
1423247
|
+
function announceCopy(message) {
|
|
1423248
|
+
const region = document.getElementById('copy-status');
|
|
1423249
|
+
if (!region) return;
|
|
1423250
|
+
region.textContent = '';
|
|
1423251
|
+
if (message) region.textContent = message;
|
|
1423252
|
+
}
|
|
1423253
|
+
|
|
1423254
|
+
// render() replaces #app.innerHTML every 5s, which would destroy a listener
|
|
1423255
|
+
// bound to the button itself \u2014 so delegate from the container instead.
|
|
1423256
|
+
let copyResetTimer = null;
|
|
1423257
|
+
document.getElementById('app').addEventListener('click', async (event) => {
|
|
1423258
|
+
const btn = event.target.closest ? event.target.closest('.copy-btn') : null;
|
|
1423259
|
+
if (!btn) return;
|
|
1423260
|
+
const text = btn.getAttribute('data-copy') || '';
|
|
1423261
|
+
if (!text) return;
|
|
1423262
|
+
|
|
1423263
|
+
const ok = await writeClipboard(text);
|
|
1423264
|
+
const icon = btn.querySelector('.copy-icon');
|
|
1423265
|
+
btn.classList.toggle('copied', ok);
|
|
1423266
|
+
if (icon) icon.textContent = ok ? '\u2705' : '\u26A0\uFE0F';
|
|
1423267
|
+
// The glyph is aria-hidden, so the outcome only reaches a screen reader
|
|
1423268
|
+
// through the live region.
|
|
1423269
|
+
const what = btn.getAttribute('data-copy-label') || 'Value';
|
|
1423270
|
+
announceCopy(ok ? what + ' copied to clipboard' : 'Could not copy ' + what);
|
|
1423271
|
+
try {
|
|
1423272
|
+
tg?.HapticFeedback?.notificationOccurred(ok ? 'success' : 'error');
|
|
1423273
|
+
} catch (e) {
|
|
1423274
|
+
// HapticFeedback is absent outside the Telegram client.
|
|
1423275
|
+
}
|
|
1423276
|
+
|
|
1423277
|
+
// Revert the affordance so a later copy still reads as feedback. The
|
|
1423278
|
+
// next poll re-renders the row anyway; this covers the gap until then.
|
|
1423279
|
+
if (copyResetTimer) clearTimeout(copyResetTimer);
|
|
1423280
|
+
copyResetTimer = setTimeout(() => {
|
|
1423281
|
+
btn.classList.remove('copied');
|
|
1423282
|
+
if (icon) icon.textContent = '\u{1F4CB}';
|
|
1423283
|
+
// Clear the announcement too, so the stale outcome is not re-read and
|
|
1423284
|
+
// the next copy of the same value announces again.
|
|
1423285
|
+
announceCopy('');
|
|
1423286
|
+
}, 1500);
|
|
1423287
|
+
});
|
|
1423288
|
+
|
|
1423134
1423289
|
load();
|
|
1423135
1423290
|
setInterval(load, 5000);
|
|
1423136
1423291
|
</script>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "visionclaw",
|
|
3
|
-
"version": "0.1.207-beta.
|
|
3
|
+
"version": "0.1.207-beta.11",
|
|
4
4
|
"description": "A personal assistant agent that runs on your desktop, receives commands from messaging channels, and executes tasks autonomously.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|