@lelouchhe/webagent 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/config.toml +12 -3
- package/dist/fonts/temml/Temml.woff2 +0 -0
- package/dist/index.html +4 -4
- package/dist/js/app.3OEVQXHK.js +4 -0
- package/dist/js/chunk.IJM5DBCO.js +173 -0
- package/dist/js/viewer.FCSSTUVY.js +1 -0
- package/dist/login.html +1 -1
- package/dist/share-viewer.html +4 -3
- package/dist/{styles.012p32dz.css → styles.00xfh3e6.css} +408 -3
- package/lib/auth-middleware.js +3 -0
- package/lib/bridge-event-config.js +29 -0
- package/lib/client-registry.js +104 -15
- package/lib/config.js +12 -5
- package/lib/image-dimensions.js +64 -0
- package/lib/model-picker.js +17 -0
- package/lib/preflight.js +41 -22
- package/lib/push-service.js +21 -69
- package/lib/routes.js +124 -12
- package/lib/server.js +19 -23
- package/lib/startup-checks.js +1 -0
- package/lib/store.js +15 -3
- package/lib/title-service.js +3 -19
- package/package.json +2 -1
- package/dist/js/app.GSAIYHML.js +0 -4
- package/dist/js/chunk.CGWFHJI2.js +0 -76
- package/dist/js/viewer.6DT53STL.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{b as m,k as f,l as g}from"./chunk.IJM5DBCO.js";import"./chunk.D4ZYHJAM.js";import"./chunk.AJZBJBMO.js";var C=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function u(t){return String(t).padStart(2,"0")}function h(t,r){if(!t)return"";let s=new Date(t),a=s.getTime();if(isNaN(a))return"";let l=Math.floor((r.getTime()-a)/1e3);if(l<60)return"just now";let i=Math.floor(l/60);if(i<60)return`${i}m ago`;let o=Math.floor(i/60);if(o<24)return`${o}h ago`;let e=Math.floor(o/24);if(e<7)return`${e}d ago`;let n=s.getUTCFullYear()===r.getUTCFullYear(),c=C[s.getUTCMonth()],d=s.getUTCDate();return n?`${c} ${d}`:`${c} ${d}, ${s.getUTCFullYear()}`}function p(t){if(!t)return"";let r=new Date(t);return isNaN(r.getTime())?"":`${r.getUTCFullYear()}-${u(r.getUTCMonth()+1)}-${u(r.getUTCDate())} ${u(r.getUTCHours())}:${u(r.getUTCMinutes())} UTC`}function y(t){let r=/^\/api\/v1\/sessions\/[^/]+\/attachments\/([A-Za-z0-9._-]+)(?:\?.*)?$/;return s=>{let a=r.exec(s);return a?`/s/${encodeURIComponent(t)}/attachments/${a[1]}`:s}}function E(t){if(typeof t.data=="string")try{return JSON.parse(t.data)}catch{return{}}return t.data}function T(t,r,s){let a=new Set;for(let n of t)if(n.type==="permission_response")try{let c=JSON.parse(n.data);typeof c.requestId=="string"&&a.add(c.requestId)}catch{}let l=new Map,i=new Map,o=null,e={rewriteAttachmentSrc:y(s),enhanceMarkdown:m,findToolCallEl:n=>l.get(n)??null,findPermissionEl:n=>i.get(n)??null,findBashEl:()=>o,isPermissionResolved:n=>a.has(n)};for(let n of t){if(!f(n.type))continue;let c=E(n),d=g(n.type,c,e);d?(r.appendChild(d),n.type==="tool_call"&&typeof c.id=="string"?l.set(c.id,d):n.type==="permission_request"&&typeof c.requestId=="string"?i.set(c.requestId,d):n.type==="bash_command"&&(o=d)):n.type==="bash_result"&&(o=null)}}async function v(){try{let e=localStorage.getItem("theme")??"auto";document.documentElement.setAttribute("data-theme",e)}catch{}let t=/^\/s\/([A-Za-z0-9_-]{24})(?:[/?#]|$)/.exec(location.pathname);if(!t){document.body.textContent="invalid share URL";return}let r=t[1],s=document.getElementById("session-info"),a=document.getElementById("messages"),l=document.querySelector(".share-footer-author"),i=document.querySelector(".share-footer-meta");if(!a)return;let o;try{let e=await fetch(`/api/v1/shared/${encodeURIComponent(r)}/events`,{headers:{Accept:"application/json"},credentials:"omit"});if(e.status===410){document.body.innerHTML="<div class='share-gone'>\u6B64\u94FE\u63A5\u5DF2\u64A4\u9500\u6216\u8FC7\u671F\u3002</div>";return}if(!e.ok){document.body.textContent=`error ${e.status}`;return}o=await e.json()}catch(e){document.body.textContent=`failed to load: ${String(e)}`;return}if(document.title=o.share.session_title?`${o.share.session_title} \u2014 shared`:"shared session",s&&(s.textContent=o.share.session_title??"(untitled)"),l){let e=o.share.display_name??"";l.textContent=e?`by ${e}`:""}if(T(o.events,a,r),i){let e=o.share.shared_at??o.share.created_at;i.textContent=h(e,new Date),i instanceof HTMLElement&&(i.title=p(e))}}v();
|
package/dist/login.html
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
media="(prefers-color-scheme: light)"
|
|
18
18
|
/>
|
|
19
19
|
<script src="/theme-init.js"></script>
|
|
20
|
-
<link rel="stylesheet" href="/styles.
|
|
20
|
+
<link rel="stylesheet" href="/styles.00xfh3e6.css" />
|
|
21
21
|
</head>
|
|
22
22
|
<body class="login-page">
|
|
23
23
|
<div class="login-card">
|
package/dist/share-viewer.html
CHANGED
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
media="(prefers-color-scheme: light)"
|
|
17
17
|
/>
|
|
18
18
|
<meta name="robots" content="noindex, nofollow" />
|
|
19
|
-
<link rel="stylesheet" href="/s/_/styles.
|
|
19
|
+
<link rel="stylesheet" href="/s/_/styles.00xfh3e6.css" />
|
|
20
20
|
<link rel="stylesheet" href="/s/_/share-viewer.00gubshk.css" />
|
|
21
|
-
<link rel="modulepreload" href="/s/_/chunk.
|
|
21
|
+
<link rel="modulepreload" href="/s/_/chunk.IJM5DBCO.js">
|
|
22
|
+
<link rel="modulepreload" href="/s/_/chunk.D4ZYHJAM.js">
|
|
22
23
|
<link rel="modulepreload" href="/s/_/chunk.AJZBJBMO.js">
|
|
23
24
|
</head>
|
|
24
25
|
<body class="share-viewer">
|
|
@@ -48,6 +49,6 @@
|
|
|
48
49
|
>
|
|
49
50
|
</div>
|
|
50
51
|
</footer>
|
|
51
|
-
<script type="module" src="/s/_/viewer.
|
|
52
|
+
<script type="module" src="/s/_/viewer.FCSSTUVY.js"></script>
|
|
52
53
|
</body>
|
|
53
54
|
</html>
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
--permission-bg: #1c1305;
|
|
16
16
|
--permission-title: #d29922;
|
|
17
17
|
--accent-soft: rgba(88, 166, 255, 0.18);
|
|
18
|
+
--user-image-max-width: 200px;
|
|
19
|
+
--user-image-max-height: 150px;
|
|
18
20
|
}
|
|
19
21
|
[data-theme="light"] {
|
|
20
22
|
--bg: #ffffff;
|
|
@@ -485,10 +487,19 @@ body {
|
|
|
485
487
|
border-left: 3px solid var(--blue);
|
|
486
488
|
border-radius: 0 var(--radius) var(--radius) 0;
|
|
487
489
|
}
|
|
488
|
-
.plan-
|
|
490
|
+
.plan-summary {
|
|
489
491
|
color: var(--blue);
|
|
490
492
|
font-weight: bold;
|
|
491
493
|
margin-bottom: 2px;
|
|
494
|
+
cursor: pointer;
|
|
495
|
+
}
|
|
496
|
+
.plan-counts {
|
|
497
|
+
color: var(--text-dim);
|
|
498
|
+
font-weight: normal;
|
|
499
|
+
margin-left: 14px;
|
|
500
|
+
}
|
|
501
|
+
.plan-entries {
|
|
502
|
+
margin-top: 2px;
|
|
492
503
|
}
|
|
493
504
|
.plan-entry {
|
|
494
505
|
padding: 1px 0;
|
|
@@ -945,8 +956,8 @@ body {
|
|
|
945
956
|
padding: 0;
|
|
946
957
|
}
|
|
947
958
|
.msg.user img.user-image {
|
|
948
|
-
max-width:
|
|
949
|
-
max-height:
|
|
959
|
+
max-width: var(--user-image-max-width);
|
|
960
|
+
max-height: var(--user-image-max-height);
|
|
950
961
|
border-radius: 4px;
|
|
951
962
|
border: 1px solid var(--border);
|
|
952
963
|
display: block;
|
|
@@ -1076,6 +1087,45 @@ body.login-page {
|
|
|
1076
1087
|
border-radius: 3px;
|
|
1077
1088
|
}
|
|
1078
1089
|
|
|
1090
|
+
/* --- Math (Temml MathML output) --- */
|
|
1091
|
+
/* Block math is wrapped in <div class="math-block"> by the renderer.
|
|
1092
|
+
Safari's overflow on <math> is broken — long equations escape the
|
|
1093
|
+
element and push the parent's width, causing page-level horizontal
|
|
1094
|
+
scroll. The wrapper div absorbs the overflow into a local scrollbar. */
|
|
1095
|
+
.math-block {
|
|
1096
|
+
overflow-x: auto;
|
|
1097
|
+
overflow-y: hidden;
|
|
1098
|
+
max-width: 100%;
|
|
1099
|
+
}
|
|
1100
|
+
/* Long inline math: wrap in an inline-block box so it can carry its own
|
|
1101
|
+
max-width + overflow-x without pushing page width. Applying these
|
|
1102
|
+
directly to <math> doesn't work on WebKit. vertical-align: middle keeps
|
|
1103
|
+
the formula visually centered with surrounding CJK/Latin text. */
|
|
1104
|
+
.math-inline {
|
|
1105
|
+
display: inline-block;
|
|
1106
|
+
max-width: 100%;
|
|
1107
|
+
overflow-x: auto;
|
|
1108
|
+
overflow-y: hidden;
|
|
1109
|
+
vertical-align: middle;
|
|
1110
|
+
}
|
|
1111
|
+
math:not(.tml-display) {
|
|
1112
|
+
max-width: 100%;
|
|
1113
|
+
}
|
|
1114
|
+
/* When Temml fails to parse math, the renderer falls back to a span
|
|
1115
|
+
containing the literal $...$ source (instead of Temml's red inline
|
|
1116
|
+
error message, which breaks paragraph flow and pollutes copy/paste).
|
|
1117
|
+
Styling is intentionally minimal — just dim text. We tried dotted /
|
|
1118
|
+
wavy / dashed underlines and thicker custom strokes, but iOS Safari
|
|
1119
|
+
silently drops non-solid text-decoration-style on inline spans
|
|
1120
|
+
(WebKit bug #233181), and any solid/colored underline that *does*
|
|
1121
|
+
render reads as "wrong" rather than "soft warning". The dim color
|
|
1122
|
+
alone signals "something off here" without distracting from the
|
|
1123
|
+
surrounding correct prose. The actual parse error is preserved in
|
|
1124
|
+
the span's title attribute for desktop hover. */
|
|
1125
|
+
.math-error {
|
|
1126
|
+
color: var(--text-dim);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1079
1129
|
/* --- highlight.js themes (vendored from highlight.js@common, BSD-3-Clause) --- */
|
|
1080
1130
|
/* light: default */
|
|
1081
1131
|
pre code.hljs {
|
|
@@ -1441,3 +1491,358 @@ code.hljs {
|
|
|
1441
1491
|
}
|
|
1442
1492
|
}
|
|
1443
1493
|
}
|
|
1494
|
+
|
|
1495
|
+
/* --- Temml (LaTeX → MathML rendering, MIT) --- */
|
|
1496
|
+
/*
|
|
1497
|
+
Temml.woff2 is a clone of KaTeX_Script-Regular, except that the code points
|
|
1498
|
+
have been changed from ASCII to Unicode Mathematical Alphanumeric Symbols Script capitals,
|
|
1499
|
+
Unicode range 1D49C to 1D4B5.
|
|
1500
|
+
*/
|
|
1501
|
+
|
|
1502
|
+
@font-face {
|
|
1503
|
+
font-family: 'Temml';
|
|
1504
|
+
src: url('/fonts/temml/Temml.woff2') format('woff2');
|
|
1505
|
+
font-weight: normal;
|
|
1506
|
+
font-style: normal;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
math {
|
|
1510
|
+
font-family: "Cambria Math", 'STIXTwoMath-Regular', 'NotoSansMath-Regular', math;
|
|
1511
|
+
font-style: normal;
|
|
1512
|
+
font-weight: normal;
|
|
1513
|
+
line-height: normal;
|
|
1514
|
+
font-size-adjust: none;
|
|
1515
|
+
text-indent: 0;
|
|
1516
|
+
text-transform: none;
|
|
1517
|
+
letter-spacing: normal;
|
|
1518
|
+
word-wrap: normal;
|
|
1519
|
+
direction: ltr;
|
|
1520
|
+
/* Prevent Firefox from omitting the dot on i or j. */
|
|
1521
|
+
font-feature-settings: "dtls" off;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
math * {
|
|
1525
|
+
border-color: currentColor;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
/* display: block is necessary in Firefox and Safari.
|
|
1529
|
+
* Not in Chromium, which recognizes display: "block math" written inline. */
|
|
1530
|
+
math.tml-display {
|
|
1531
|
+
display: block;
|
|
1532
|
+
width: 100%;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
*.mathcal {
|
|
1536
|
+
/* NotoSans */
|
|
1537
|
+
font-feature-settings: 'ss01';
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
math .mathscr {
|
|
1541
|
+
font-family: "Temml";
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
mo.tml-prime {
|
|
1545
|
+
font-family: Temml;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
/* Cramped superscripts in WebKit */
|
|
1549
|
+
mfrac > :nth-child(2),
|
|
1550
|
+
msqrt,
|
|
1551
|
+
mover > :first-child {
|
|
1552
|
+
math-shift: compact
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
.menclose {
|
|
1556
|
+
display: inline-block;
|
|
1557
|
+
position: relative;
|
|
1558
|
+
padding: 0.5ex 0ex;
|
|
1559
|
+
}
|
|
1560
|
+
.tml-cancelto {
|
|
1561
|
+
display: inline-block;
|
|
1562
|
+
position: absolute;
|
|
1563
|
+
top: 0;
|
|
1564
|
+
left: 0;
|
|
1565
|
+
padding: 0.5ex 0ex;
|
|
1566
|
+
background-color: currentColor;
|
|
1567
|
+
/* Use the SVG as an alpha mask (painted by background-color) */
|
|
1568
|
+
-webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><defs><marker id='a' markerHeight='5' markerUnits='strokeWidth' markerWidth='7' orient='auto' refX='7' refY='2.5'><path fill='black' d='m0 0 7 2.5L0 5z'/></marker></defs><line x2='100%25' y1='100%25' stroke='black' stroke-width='.06em' marker-end='url(%23a)' vector-effect='non-scaling-stroke'/></svg>");
|
|
1569
|
+
mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><defs><marker id='a' markerHeight='5' markerUnits='strokeWidth' markerWidth='7' orient='auto' refX='7' refY='2.5'><path fill='black' d='m0 0 7 2.5L0 5z'/></marker></defs><line x2='100%25' y1='100%25' stroke='black' stroke-width='.06em' marker-end='url(%23a)' vector-effect='non-scaling-stroke'/></svg>");
|
|
1570
|
+
-webkit-mask-repeat: no-repeat;
|
|
1571
|
+
mask-repeat: no-repeat;
|
|
1572
|
+
-webkit-mask-size: 100% 100%;
|
|
1573
|
+
mask-size: 100% 100%;
|
|
1574
|
+
-webkit-mask-position: 0 0;
|
|
1575
|
+
mask-position: 0 0;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
@supports (-moz-appearance: none) {
|
|
1579
|
+
/* \vec w/o italic correction for Firefox */
|
|
1580
|
+
.tml-vec {
|
|
1581
|
+
transform: scale(0.75)
|
|
1582
|
+
}
|
|
1583
|
+
/* Fix \cancelto in Firefox */
|
|
1584
|
+
.ff-narrow {
|
|
1585
|
+
width: 0em;
|
|
1586
|
+
}
|
|
1587
|
+
.ff-nudge-left {
|
|
1588
|
+
margin-left: -0.2em;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
@supports (not (-moz-appearance: none)) {
|
|
1593
|
+
/* Chromium and WebKit */
|
|
1594
|
+
/* prime vertical alignment */
|
|
1595
|
+
mo.tml-prime {
|
|
1596
|
+
font-family: Temml;
|
|
1597
|
+
}
|
|
1598
|
+
/* Italic correction on superscripts */
|
|
1599
|
+
.tml-sml-pad {
|
|
1600
|
+
padding-left: 0.05em;
|
|
1601
|
+
}
|
|
1602
|
+
.tml-med-pad {
|
|
1603
|
+
padding-left: 0.10em;
|
|
1604
|
+
}
|
|
1605
|
+
.tml-lrg-pad {
|
|
1606
|
+
padding-left: 0.15em;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
@supports (-webkit-backdrop-filter: blur(1px)) {
|
|
1611
|
+
/* WebKit vertical & italic correction on accents */
|
|
1612
|
+
.wbk-acc {
|
|
1613
|
+
/* lower by x-height distance */
|
|
1614
|
+
transform: translate(0em, 0.431em);
|
|
1615
|
+
}
|
|
1616
|
+
.wbk-sml {
|
|
1617
|
+
transform: translate(0.07em, 0);
|
|
1618
|
+
}
|
|
1619
|
+
.wbk-sml-acc {
|
|
1620
|
+
transform: translate(0.07em, 0.431em);
|
|
1621
|
+
}
|
|
1622
|
+
.wbk-sml-vec {
|
|
1623
|
+
transform: scale(0.75) translate(0.07em, 0);
|
|
1624
|
+
}
|
|
1625
|
+
.wbk-med {
|
|
1626
|
+
transform: translate(0.14em, 0);
|
|
1627
|
+
}
|
|
1628
|
+
.wbk-med-acc {
|
|
1629
|
+
transform: translate(0.14em, 0.431em);
|
|
1630
|
+
}
|
|
1631
|
+
.wbk-med-vec {
|
|
1632
|
+
transform: scale(0.75) translate(0.14em, 0);
|
|
1633
|
+
}
|
|
1634
|
+
.wbk-lrg {
|
|
1635
|
+
transform: translate(0.21em, 0);
|
|
1636
|
+
}
|
|
1637
|
+
.wbk-lrg-acc {
|
|
1638
|
+
transform: translate(0.21em, 0.431em);
|
|
1639
|
+
}
|
|
1640
|
+
.wbk-lrg-vec {
|
|
1641
|
+
transform: scale(0.75) translate(0.21em, 0);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
/* \cancel & \phase use background images. Get them to print. */
|
|
1646
|
+
menclose {
|
|
1647
|
+
-webkit-print-color-adjust: exact; /* Chrome & Edge */
|
|
1648
|
+
print-color-adjust: exact;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
/* Array cell justification in Firefox & WebKit */
|
|
1652
|
+
.tml-right {
|
|
1653
|
+
text-align: right;
|
|
1654
|
+
}
|
|
1655
|
+
.tml-left {
|
|
1656
|
+
text-align: left;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
/* For CD labels that grow to the left in Firefox and WebKit */
|
|
1660
|
+
.tml-shift-left { margin-left:-200% }
|
|
1661
|
+
|
|
1662
|
+
/* Styles for Chromium only */
|
|
1663
|
+
@supports (not (-webkit-backdrop-filter: blur(1px))) and (not (-moz-appearance: none)) {
|
|
1664
|
+
/* Italic correction on accents */
|
|
1665
|
+
.chr-sml {
|
|
1666
|
+
transform: translate(0.07em, 0)
|
|
1667
|
+
}
|
|
1668
|
+
.chr-sml-vec {
|
|
1669
|
+
transform: scale(0.75) translate(0.07em, 0)
|
|
1670
|
+
}
|
|
1671
|
+
.chr-med {
|
|
1672
|
+
transform: translate(0.14em, 0)
|
|
1673
|
+
}
|
|
1674
|
+
.chr-med-vec {
|
|
1675
|
+
transform: scale(0.75) translate(0.14em, 0)
|
|
1676
|
+
}
|
|
1677
|
+
.chr-lrg {
|
|
1678
|
+
transform: translate(0.21em, 0)
|
|
1679
|
+
}
|
|
1680
|
+
.chr-lrg-vec {
|
|
1681
|
+
transform: scale(0.75) translate(0.21em, 0)
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
/* For CD labels that grow to the left */
|
|
1685
|
+
.tml-shift-left { margin-left:-100% }
|
|
1686
|
+
|
|
1687
|
+
/* MathML Core & Chromium do not support the MathML 3.0 element <menclose> attributes. */
|
|
1688
|
+
/* So use styles. */
|
|
1689
|
+
menclose {
|
|
1690
|
+
position: relative;
|
|
1691
|
+
padding: 0.5ex 0ex;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
.tml-overline {
|
|
1695
|
+
padding: 0.1em 0 0 0;
|
|
1696
|
+
border-top: 0.065em solid;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
.tml-underline {
|
|
1700
|
+
padding: 0 0 0.1em 0;
|
|
1701
|
+
border-bottom: 0.065em solid;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
.tml-cancel {
|
|
1705
|
+
display: inline-block;
|
|
1706
|
+
position: absolute;
|
|
1707
|
+
left: 0.5px;
|
|
1708
|
+
bottom: 0;
|
|
1709
|
+
width: 100%;
|
|
1710
|
+
height: 100%;
|
|
1711
|
+
background-color: currentColor;
|
|
1712
|
+
}
|
|
1713
|
+
.upstrike {
|
|
1714
|
+
clip-path: polygon(0.05em 100%, 0em calc(100% - 0.05em), calc(100% - 0.05em) 0em, 100% 0.05em);
|
|
1715
|
+
}
|
|
1716
|
+
.downstrike {
|
|
1717
|
+
clip-path: polygon(0em 0.05em, 0.05em 0em, 100% calc(100% - 0.05em), calc(100% - 0.05em) 100%);
|
|
1718
|
+
}
|
|
1719
|
+
.sout {
|
|
1720
|
+
clip-path: polygon(0em calc(55% + 0.0333em), 0em calc(55% - 0.0333em), 100% calc(55% - 0.0333em), 100% calc(55% + 0.0333em));
|
|
1721
|
+
}
|
|
1722
|
+
.tml-xcancel {
|
|
1723
|
+
clip-path: polygon(0.05em 0em, 0em 0.05em, calc(50% - 0.05em) 50%, 0em calc(100% - 0.05em), 0.05em 100%, 50% calc(50% + 0.05em), calc(100% - 0.05em) 100%, 100% calc(100% - 0.05em), calc(50% + 0.05em) 50%, 100% 0.05em, calc(100% - 0.05em) 0%, 50% calc(50% - 0.05em));
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
.longdiv-top {
|
|
1727
|
+
border-top: 0.067em solid;
|
|
1728
|
+
padding: 0.1em 0.2em 0.2em 0.433em;
|
|
1729
|
+
}
|
|
1730
|
+
.longdiv-arc {
|
|
1731
|
+
position: absolute;
|
|
1732
|
+
top: 0;
|
|
1733
|
+
bottom: 0.1em;
|
|
1734
|
+
left: -0.4em;
|
|
1735
|
+
width: 0.7em;
|
|
1736
|
+
border: 0.067em solid;
|
|
1737
|
+
transform: translateY(-0.067em);
|
|
1738
|
+
border-radius: 70%;
|
|
1739
|
+
clip-path: inset(0 0 0 0.4em);
|
|
1740
|
+
box-sizing: border-box;}
|
|
1741
|
+
.menclose {display: inline-block;
|
|
1742
|
+
text-align: left;
|
|
1743
|
+
position: relative;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
.phasor-bottom {
|
|
1747
|
+
border-bottom: 0.067em solid;
|
|
1748
|
+
padding: 0.2em 0.2em 0.1em 0.6em;
|
|
1749
|
+
}
|
|
1750
|
+
.phasor-angle {
|
|
1751
|
+
display: inline-block;
|
|
1752
|
+
position: absolute;
|
|
1753
|
+
left: 0.5px;
|
|
1754
|
+
bottom: -0.04em;
|
|
1755
|
+
height: 100%;
|
|
1756
|
+
aspect-ratio: 0.5;
|
|
1757
|
+
background-color: currentColor;
|
|
1758
|
+
clip-path: polygon(0.05em 100%, 0em calc(100% - 0.05em), calc(100% - 0.05em) 0em, 100% 0.05em);
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
.tml-fbox {
|
|
1762
|
+
padding: 3pt;
|
|
1763
|
+
border: 1px solid;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
.circle-pad {
|
|
1767
|
+
padding: 0.267em;
|
|
1768
|
+
}
|
|
1769
|
+
.textcircle {
|
|
1770
|
+
position: absolute;
|
|
1771
|
+
top: 0;
|
|
1772
|
+
bottom: 0;
|
|
1773
|
+
right: 0;
|
|
1774
|
+
left: 0;
|
|
1775
|
+
border: 0.067em solid;
|
|
1776
|
+
border-radius: 50%;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
.actuarial {
|
|
1780
|
+
padding: 0.03889em 0.03889em 0 0.03889em;
|
|
1781
|
+
border-width: 0.08em 0.08em 0em 0em;
|
|
1782
|
+
border-style: solid;
|
|
1783
|
+
margin-right: 0.03889em;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
/* Stretch \widetilde */
|
|
1787
|
+
.tml-crooked-2 {
|
|
1788
|
+
transform: scale(2.0, 1.1)
|
|
1789
|
+
}
|
|
1790
|
+
.tml-crooked-3 {
|
|
1791
|
+
transform: scale(3.0, 1.3)
|
|
1792
|
+
}
|
|
1793
|
+
.tml-crooked-4 {
|
|
1794
|
+
transform: scale(4.0, 1.4)
|
|
1795
|
+
}
|
|
1796
|
+
/* set array cell justification */
|
|
1797
|
+
.tml-right {
|
|
1798
|
+
text-align: -webkit-right;
|
|
1799
|
+
}
|
|
1800
|
+
.tml-left {
|
|
1801
|
+
text-align: -webkit-left;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
.special-fraction {
|
|
1806
|
+
font-family: 'STIX TWO', 'Times New Roman', Times, Tinos, serif;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
/* flex-wrap for line-breaking in Chromium */
|
|
1810
|
+
math {
|
|
1811
|
+
display: inline-flex;
|
|
1812
|
+
flex-wrap: wrap;
|
|
1813
|
+
align-items: baseline;
|
|
1814
|
+
}
|
|
1815
|
+
math > mrow {
|
|
1816
|
+
padding: 0.5ex 0ex;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
/* Default mtd top padding is 0.5ex per MathML-Core and user-agent CSS */
|
|
1820
|
+
/* We adjust for jot and small */
|
|
1821
|
+
mtable.tml-jot mtd {
|
|
1822
|
+
padding-top: 0.7ex;
|
|
1823
|
+
padding-bottom: 0.7ex;
|
|
1824
|
+
}
|
|
1825
|
+
mtable.tml-small mtd {
|
|
1826
|
+
padding-top: 0.35ex;
|
|
1827
|
+
padding-bottom: 0.35ex;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
/* Firefox */
|
|
1831
|
+
@-moz-document url-prefix() {
|
|
1832
|
+
/* Avoid flex-wrap */
|
|
1833
|
+
math { display: inline; }
|
|
1834
|
+
math > mrow { padding: 0 }
|
|
1835
|
+
/* Adjust Firefox spacing between array rows */
|
|
1836
|
+
mtd, mtable.tml-small mtd { padding-top: 0; padding-bottom: 0; }
|
|
1837
|
+
mtable.tml-jot mtd { padding-top: 0.2ex; padding-bottom: 0.ex; }
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
/* AMS environment auto-numbering via CSS counter. */
|
|
1841
|
+
.tml-eqn::before {
|
|
1842
|
+
counter-increment: tmlEqnNo;
|
|
1843
|
+
content: "(" counter(tmlEqnNo) ")";
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
body {
|
|
1847
|
+
counter-reset: tmlEqnNo;
|
|
1848
|
+
}
|
package/lib/auth-middleware.js
CHANGED
|
@@ -18,6 +18,9 @@ const WHITELIST = [
|
|
|
18
18
|
{ method: "GET", test: (p) => p === "/styles.css" },
|
|
19
19
|
// Icons directory (no traversal: enforced by the check below)
|
|
20
20
|
{ method: "GET", test: (p) => /^\/icons\/[A-Za-z0-9._-]+$/.test(p) },
|
|
21
|
+
// Temml math font (loaded from CSS @font-face, no Authorization header
|
|
22
|
+
// available). Single fixed file: dist/fonts/temml/Temml.woff2.
|
|
23
|
+
{ method: "GET", test: (p) => p === "/fonts/temml/Temml.woff2" },
|
|
21
24
|
// SSE streams — authenticated via short-lived ticket in query string
|
|
22
25
|
// (EventSource cannot send custom headers).
|
|
23
26
|
{ method: "GET", test: (p) => p === "/api/v1/events/stream" },
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Builds the EventHandlerConfig object passed to handleAgentEvent for every
|
|
2
|
+
// bridge event. Lives in its own file so a unit test can lock the wiring
|
|
3
|
+
// without booting the full server.
|
|
4
|
+
//
|
|
5
|
+
// Why this matters: missing fields on the inline config object handed to
|
|
6
|
+
// handleAgentEvent are silent at runtime — the affected feature just stops
|
|
7
|
+
// working, and the bug only surfaces in dogfood. A small structural test
|
|
8
|
+
// next to this builder makes future "merge --theirs" / refactor passes
|
|
9
|
+
// fail loudly instead.
|
|
10
|
+
import { log } from "./log.js";
|
|
11
|
+
export function buildBridgeEventHandlerConfig(deps) {
|
|
12
|
+
return {
|
|
13
|
+
cancelTimeout: deps.cancelTimeout,
|
|
14
|
+
recentPathsLimit: deps.recentPathsLimit,
|
|
15
|
+
attachmentInterceptor: {
|
|
16
|
+
counters: deps.attachmentInterceptorCounters,
|
|
17
|
+
logger: log.scope("attachment-interceptor"),
|
|
18
|
+
onSchemaDrift: (ctx) => {
|
|
19
|
+
if (!deps.shouldLogSchemaDrift())
|
|
20
|
+
return;
|
|
21
|
+
log
|
|
22
|
+
.scope("attachment-interceptor")
|
|
23
|
+
.error("schema drift detected — rawInput has no known path key", {
|
|
24
|
+
ctx,
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
package/lib/client-registry.js
CHANGED
|
@@ -3,28 +3,37 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Tracks per-client metadata that survives SSE disconnect:
|
|
5
5
|
* - capabilities advertised by the client on /hello.
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
6
|
+
* - visible / active / visibleSince: identity-layer visibility state used
|
|
7
|
+
* by TTS dispatch (voice branch) and push suppression (main).
|
|
8
8
|
*
|
|
9
|
-
* Lifecycle: clients call /hello on SSE connect (register) and
|
|
10
|
-
*
|
|
11
|
-
* in the registry until an explicit
|
|
12
|
-
* responsibility). This lets visibility
|
|
9
|
+
* Lifecycle: clients call /hello on SSE connect (register) and POST
|
|
10
|
+
* /visibility on visibilitychange + 15s heartbeat. SSE disconnect does
|
|
11
|
+
* not remove a client — it stays in the registry until an explicit
|
|
12
|
+
* /goodbye or TTL eviction (caller's responsibility). This lets visibility
|
|
13
|
+
* state outlive transient drops.
|
|
13
14
|
*/
|
|
14
15
|
export class ClientRegistry {
|
|
15
16
|
clients = new Map();
|
|
17
|
+
visibilityTtlMs;
|
|
18
|
+
now;
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.visibilityTtlMs = options.visibilityTtlMs ?? 60_000;
|
|
21
|
+
this.now = options.now ?? Date.now;
|
|
22
|
+
}
|
|
16
23
|
register(id, data) {
|
|
17
24
|
const existing = this.clients.get(id);
|
|
18
25
|
if (existing) {
|
|
19
26
|
existing.capabilities = data.capabilities;
|
|
20
|
-
existing.lastSeen =
|
|
27
|
+
existing.lastSeen = this.now();
|
|
21
28
|
return existing;
|
|
22
29
|
}
|
|
23
30
|
const entry = {
|
|
24
31
|
id,
|
|
25
32
|
capabilities: data.capabilities,
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
visible: false,
|
|
34
|
+
active: null,
|
|
35
|
+
visibleSince: 0,
|
|
36
|
+
lastSeen: this.now(),
|
|
28
37
|
};
|
|
29
38
|
this.clients.set(id, entry);
|
|
30
39
|
return entry;
|
|
@@ -32,24 +41,104 @@ export class ClientRegistry {
|
|
|
32
41
|
remove(id) {
|
|
33
42
|
this.clients.delete(id);
|
|
34
43
|
}
|
|
35
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Atomic visibility setter. Mirrors PushService.updateClient semantics:
|
|
46
|
+
* - `visible` omitted = preserve; bool = set (and stamp/clear visibleSince).
|
|
47
|
+
* - `active` omitted = preserve; null = clear; string = replace.
|
|
48
|
+
* - Returns `becameVisibleFor=X` only on first transition into
|
|
49
|
+
* (visible:true, active:X) — heartbeat refreshes return null so
|
|
50
|
+
* callers can fire edge-triggered side effects exactly once.
|
|
51
|
+
* - Session-switch while visible (active X→Y) restarts the TTL clock
|
|
52
|
+
* even when the patch doesn't carry an explicit visible:true.
|
|
53
|
+
*
|
|
54
|
+
* No-op on unknown client.
|
|
55
|
+
*/
|
|
56
|
+
setVisibility(id, patch) {
|
|
36
57
|
const entry = this.clients.get(id);
|
|
37
58
|
if (!entry)
|
|
38
|
-
return;
|
|
39
|
-
entry.
|
|
40
|
-
|
|
59
|
+
return { becameVisibleFor: null };
|
|
60
|
+
const wasVisibleForSession = entry.visible && entry.active != null ? entry.active : null;
|
|
61
|
+
if (patch.visible !== undefined) {
|
|
62
|
+
entry.visible = patch.visible;
|
|
63
|
+
entry.visibleSince = patch.visible ? this.now() : 0;
|
|
64
|
+
}
|
|
65
|
+
if (patch.active !== undefined) {
|
|
66
|
+
entry.active = patch.active;
|
|
67
|
+
}
|
|
68
|
+
const becameVisibleFor = entry.visible &&
|
|
69
|
+
entry.active != null &&
|
|
70
|
+
entry.active !== wasVisibleForSession
|
|
71
|
+
? entry.active
|
|
72
|
+
: null;
|
|
73
|
+
if (becameVisibleFor) {
|
|
74
|
+
// Any transition into "visible + active=X" restarts TTL — including
|
|
75
|
+
// session-switches that arrive without an explicit visible:true.
|
|
76
|
+
entry.visibleSince = this.now();
|
|
77
|
+
}
|
|
78
|
+
entry.lastSeen = this.now();
|
|
79
|
+
return { becameVisibleFor };
|
|
80
|
+
}
|
|
81
|
+
/** Is this specific client currently visible & viewing `sessionId` & fresh? */
|
|
82
|
+
isVisibleForSession(id, sessionId) {
|
|
83
|
+
const entry = this.clients.get(id);
|
|
84
|
+
if (!entry)
|
|
85
|
+
return false;
|
|
86
|
+
if (!entry.visible)
|
|
87
|
+
return false;
|
|
88
|
+
if (entry.active !== sessionId)
|
|
89
|
+
return false;
|
|
90
|
+
if (this.now() - entry.visibleSince > this.visibilityTtlMs)
|
|
91
|
+
return false;
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
/** Is at least one fresh visible client viewing `sessionId`? */
|
|
95
|
+
isSessionVisibleToAnyClient(sessionId) {
|
|
96
|
+
const now = this.now();
|
|
97
|
+
for (const e of this.clients.values()) {
|
|
98
|
+
if (!e.visible)
|
|
99
|
+
continue;
|
|
100
|
+
if (e.active !== sessionId)
|
|
101
|
+
continue;
|
|
102
|
+
if (now - e.visibleSince > this.visibilityTtlMs)
|
|
103
|
+
continue;
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
/** Is this specific client currently fresh-visible (any session)? */
|
|
109
|
+
isClientVisible(id) {
|
|
110
|
+
const entry = this.clients.get(id);
|
|
111
|
+
if (!entry)
|
|
112
|
+
return false;
|
|
113
|
+
if (!entry.visible)
|
|
114
|
+
return false;
|
|
115
|
+
if (this.now() - entry.visibleSince > this.visibilityTtlMs)
|
|
116
|
+
return false;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
/** Is at least one fresh visible client connected (any session)? */
|
|
120
|
+
hasAnyVisibleClient() {
|
|
121
|
+
const now = this.now();
|
|
122
|
+
for (const e of this.clients.values()) {
|
|
123
|
+
if (!e.visible)
|
|
124
|
+
continue;
|
|
125
|
+
if (now - e.visibleSince > this.visibilityTtlMs)
|
|
126
|
+
continue;
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
41
130
|
}
|
|
42
131
|
updateCapabilities(id, caps) {
|
|
43
132
|
const entry = this.clients.get(id);
|
|
44
133
|
if (!entry)
|
|
45
134
|
return;
|
|
46
135
|
entry.capabilities = caps;
|
|
47
|
-
entry.lastSeen =
|
|
136
|
+
entry.lastSeen = this.now();
|
|
48
137
|
}
|
|
49
138
|
touch(id) {
|
|
50
139
|
const entry = this.clients.get(id);
|
|
51
140
|
if (entry)
|
|
52
|
-
entry.lastSeen =
|
|
141
|
+
entry.lastSeen = this.now();
|
|
53
142
|
}
|
|
54
143
|
get(id) {
|
|
55
144
|
return this.clients.get(id);
|