@lelouchhe/webagent 0.7.0 → 0.9.0
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 +4 -1
- package/dist/index.html +31 -6
- package/dist/js/app.QC7IRDTP.js +5 -0
- package/dist/js/chunk.3CLGCUHW.js +1 -0
- package/dist/js/chunk.AOTG3PL7.js +20 -0
- package/dist/js/{chunk.3CCHZCG7.js → chunk.S5LRNRJI.js} +42 -41
- package/dist/js/{login.2WA6DTGM.js → login.WMURU4NI.js} +1 -1
- package/dist/js/viewer.GP5VXAUY.js +1 -0
- package/dist/login.html +2 -2
- package/dist/share-viewer.html +4 -4
- package/dist/{styles.01ky3cex.css → styles.00nlhhf3.css} +261 -6
- package/dist/sw.js +19 -9
- package/lib/agent-key.js +6 -0
- package/lib/attachments.js +44 -7
- package/lib/auth-middleware.js +9 -2
- package/lib/bridge.js +164 -33
- package/lib/event-handler.js +91 -18
- package/lib/files/limits.js +15 -0
- package/lib/files/paths.js +155 -0
- package/lib/files/routes.js +232 -0
- package/lib/home-path.js +35 -0
- package/lib/http-status.js +1 -0
- package/lib/message-cleanup.js +5 -4
- package/lib/routes.js +316 -72
- package/lib/server.js +26 -23
- package/lib/session-manager.js +217 -44
- package/lib/session-state.js +99 -5
- package/lib/share/routes.js +12 -0
- package/lib/sse-manager.js +31 -7
- package/lib/store.js +124 -41
- package/lib/title-service.js +3 -3
- package/package.json +2 -1
- package/dist/js/app.KFQAFHA3.js +0 -4
- package/dist/js/chunk.UMQMOGWO.js +0 -1
- package/dist/js/viewer.T32M5AZZ.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{b as r,c as i}from"./chunk.
|
|
1
|
+
import{b as r,c as i}from"./chunk.3CLGCUHW.js";import"./chunk.AJZBJBMO.js";localStorage.getItem(r)&&location.replace("/");var s=document.getElementById("login-form"),n=document.getElementById("token-input"),o=document.getElementById("error"),t=document.getElementById("submit-btn");function u(e){o&&(o.textContent=e,o.style.display=e?"block":"none")}s&&n&&t&&(s.addEventListener("submit",e=>{e.preventDefault(),u(""),t.disabled=!0,t.textContent="Verifying\u2026",(async()=>{try{let l=await i(n.value);l.ok?location.replace("/"):u(l.error)}finally{t.disabled=!1,t.textContent="Sign in"}})()}),n.focus());
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{c as g,e as h,n as y,p as T,q as C}from"./chunk.S5LRNRJI.js";import{a as p}from"./chunk.3CLGCUHW.js";import"./chunk.AJZBJBMO.js";var $=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function f(t){return String(t).padStart(2,"0")}function E(t,n){if(!t)return"";let s=new Date(t),i=s.getTime();if(isNaN(i))return"";let l=Math.floor((n.getTime()-i)/1e3);if(l<60)return"just now";let c=Math.floor(l/60);if(c<60)return`${c}m ago`;let o=Math.floor(c/60);if(o<24)return`${o}h ago`;let e=Math.floor(o/24);if(e<7)return`${e}d ago`;let m=s.getUTCFullYear()===n.getUTCFullYear(),r=$[s.getUTCMonth()],a=s.getUTCDate();return m?`${r} ${a}`:`${r} ${a}, ${s.getUTCFullYear()}`}function _(t){if(!t)return"";let n=new Date(t);return isNaN(n.getTime())?"":`${n.getUTCFullYear()}-${f(n.getUTCMonth()+1)}-${f(n.getUTCDate())} ${f(n.getUTCHours())}:${f(n.getUTCMinutes())} UTC`}function S(t){let n=/^\/api\/v1\/sessions\/[^/]+\/attachments\/([A-Za-z0-9._-]+)(?:\?.*)?$/;return s=>{let i=n.exec(s);return i?`/s/${encodeURIComponent(t)}/attachments/${i[1]}`:s}}function w(t){if(typeof t.data=="string")try{return JSON.parse(t.data)}catch{return{}}return t.data}function x(t,n,s,i){if(t.type!=="assistant_message"||n?.type!=="assistant_message"||t.seq!==n.seq+1||!s?.classList.contains("assistant"))return!1;let l=(s.getAttribute("data-raw")??"")+(typeof i.text=="string"?i.text:"");return s.setAttribute("data-raw",l),C(s,l,void 0,!0),g(s),!0}function v(t,n,s){let i=new Set;for(let r of t)if(r.type==="permission_response")try{let a=JSON.parse(r.data);typeof a.requestId=="string"&&i.add(a.requestId)}catch{}let l=new Map,c=new Map,o=null,e=null,m={rewriteAttachmentSrc:S(s),enhanceMarkdown:g,findToolCallEl:r=>l.get(r)??null,findPermissionEl:r=>c.get(r)??null,findBashEl:()=>o,isPermissionResolved:r=>i.has(r),finalAnswerToolText:null};for(let r=0;r<t.length;r++){let a=t[r];if(!y(a.type))continue;let u=w(a),A=r>0?t[r-1]:null,M=n.lastElementChild;if(x(a,A,M,u)){e=null;continue}m.finalAnswerToolText=a.type==="assistant_message"?e:null;let d=T(a.type,u,m);d?(n.appendChild(d),a.type==="tool_call"&&typeof u.id=="string"?l.set(u.id,d):a.type==="permission_request"&&typeof u.requestId=="string"?c.set(u.requestId,d):a.type==="bash_command"&&(o=d)):a.type==="bash_result"&&(o=null),a.type==="tool_call_update"?e=h(typeof u.status=="string"?u.status:"",Array.isArray(u.content)?u.content:void 0):e=null}}async function U(){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 n=t[1],s=document.getElementById("session-info"),i=document.getElementById("messages"),l=document.querySelector(".share-footer-author"),c=document.querySelector(".share-footer-meta");if(!i)return;let o;try{let e=await fetch(`/api/v1/shared/${encodeURIComponent(n)}/events`,{headers:{Accept:"application/json"},credentials:"omit"});if(e.status===p.GONE){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(v(o.events,i,n),c){let e=o.share.shared_at??o.share.created_at;c.textContent=E(e,new Date),c instanceof HTMLElement&&(c.title=_(e))}}U();
|
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.00nlhhf3.css" />
|
|
21
21
|
</head>
|
|
22
22
|
<body class="login-page">
|
|
23
23
|
<div class="login-card">
|
|
@@ -44,6 +44,6 @@
|
|
|
44
44
|
<code>webagent --create-token <name></code> on the server.
|
|
45
45
|
</p>
|
|
46
46
|
</div>
|
|
47
|
-
<script type="module" src="/js/login.
|
|
47
|
+
<script type="module" src="/js/login.WMURU4NI.js"></script>
|
|
48
48
|
</body>
|
|
49
49
|
</html>
|
package/dist/share-viewer.html
CHANGED
|
@@ -16,10 +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.00nlhhf3.css" />
|
|
20
20
|
<link rel="stylesheet" href="/s/_/share-viewer.00gubshk.css" />
|
|
21
|
-
<link rel="modulepreload" href="/s/_/chunk.
|
|
22
|
-
<link rel="modulepreload" href="/s/_/chunk.
|
|
21
|
+
<link rel="modulepreload" href="/s/_/chunk.S5LRNRJI.js">
|
|
22
|
+
<link rel="modulepreload" href="/s/_/chunk.3CLGCUHW.js">
|
|
23
23
|
<link rel="modulepreload" href="/s/_/chunk.AJZBJBMO.js">
|
|
24
24
|
</head>
|
|
25
25
|
<body class="share-viewer">
|
|
@@ -49,6 +49,6 @@
|
|
|
49
49
|
>
|
|
50
50
|
</div>
|
|
51
51
|
</footer>
|
|
52
|
-
<script type="module" src="/s/_/viewer.
|
|
52
|
+
<script type="module" src="/s/_/viewer.GP5VXAUY.js"></script>
|
|
53
53
|
</body>
|
|
54
54
|
</html>
|
|
@@ -84,6 +84,32 @@ body {
|
|
|
84
84
|
font-weight: bold;
|
|
85
85
|
color: var(--green);
|
|
86
86
|
}
|
|
87
|
+
#inbox-btn {
|
|
88
|
+
display: inline-flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
gap: 4px;
|
|
91
|
+
min-height: 28px;
|
|
92
|
+
padding: 2px 6px;
|
|
93
|
+
margin-left: -6px;
|
|
94
|
+
border: 0;
|
|
95
|
+
background: transparent;
|
|
96
|
+
color: inherit;
|
|
97
|
+
font: inherit;
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
}
|
|
100
|
+
#inbox-btn:hover .logo,
|
|
101
|
+
#inbox-btn:focus-visible .logo {
|
|
102
|
+
color: var(--text);
|
|
103
|
+
}
|
|
104
|
+
#inbox-btn:focus-visible {
|
|
105
|
+
outline: 1px solid var(--text-dim);
|
|
106
|
+
outline-offset: 1px;
|
|
107
|
+
}
|
|
108
|
+
#inbox-count {
|
|
109
|
+
color: var(--red);
|
|
110
|
+
font-size: 13px;
|
|
111
|
+
font-weight: 600;
|
|
112
|
+
}
|
|
87
113
|
.header-side {
|
|
88
114
|
min-width: 0;
|
|
89
115
|
display: flex;
|
|
@@ -139,7 +165,8 @@ body {
|
|
|
139
165
|
width: 100%;
|
|
140
166
|
text-align: center;
|
|
141
167
|
}
|
|
142
|
-
#theme-btn
|
|
168
|
+
#theme-btn,
|
|
169
|
+
#file-viewer-close {
|
|
143
170
|
background: transparent;
|
|
144
171
|
border: none;
|
|
145
172
|
color: var(--text-dim);
|
|
@@ -148,7 +175,8 @@ body {
|
|
|
148
175
|
font-family: inherit;
|
|
149
176
|
padding: 2px 6px;
|
|
150
177
|
}
|
|
151
|
-
#theme-btn:hover
|
|
178
|
+
#theme-btn:hover,
|
|
179
|
+
#file-viewer-close:hover {
|
|
152
180
|
color: var(--text);
|
|
153
181
|
}
|
|
154
182
|
@media (max-width: 640px) {
|
|
@@ -312,6 +340,22 @@ body {
|
|
|
312
340
|
font-weight: normal;
|
|
313
341
|
}
|
|
314
342
|
|
|
343
|
+
.msg.assistant .subagent-result {
|
|
344
|
+
color: var(--text-dim);
|
|
345
|
+
font-size: 14px;
|
|
346
|
+
margin: 2px 0;
|
|
347
|
+
}
|
|
348
|
+
.msg.assistant .subagent-result summary {
|
|
349
|
+
cursor: pointer;
|
|
350
|
+
}
|
|
351
|
+
.msg.assistant .subagent-result-content {
|
|
352
|
+
color: var(--text);
|
|
353
|
+
margin: 4px 0 4px 16px;
|
|
354
|
+
}
|
|
355
|
+
.msg.assistant .assistant-continuation {
|
|
356
|
+
margin-top: 4px;
|
|
357
|
+
}
|
|
358
|
+
|
|
315
359
|
/* Thinking */
|
|
316
360
|
.thinking {
|
|
317
361
|
color: var(--text-dim);
|
|
@@ -494,6 +538,12 @@ body {
|
|
|
494
538
|
margin-bottom: 2px;
|
|
495
539
|
cursor: pointer;
|
|
496
540
|
}
|
|
541
|
+
.plan-summary::marker {
|
|
542
|
+
content: "";
|
|
543
|
+
}
|
|
544
|
+
.plan-summary::-webkit-details-marker {
|
|
545
|
+
display: none;
|
|
546
|
+
}
|
|
497
547
|
.plan-counts {
|
|
498
548
|
color: var(--text-dim);
|
|
499
549
|
font-weight: normal;
|
|
@@ -505,6 +555,28 @@ body {
|
|
|
505
555
|
.plan-entry {
|
|
506
556
|
padding: 1px 0;
|
|
507
557
|
}
|
|
558
|
+
.plan-status-pending {
|
|
559
|
+
color: var(--text-dim);
|
|
560
|
+
}
|
|
561
|
+
.plan-status-in_progress .plan-symbol,
|
|
562
|
+
.plan-counts .plan-status-in_progress {
|
|
563
|
+
color: var(--blue);
|
|
564
|
+
}
|
|
565
|
+
.plan-status-in_progress .plan-symbol {
|
|
566
|
+
display: inline-block;
|
|
567
|
+
background: var(--accent-soft);
|
|
568
|
+
border-radius: 2px;
|
|
569
|
+
}
|
|
570
|
+
.plan-status-completed .plan-symbol,
|
|
571
|
+
.plan-counts .plan-status-completed {
|
|
572
|
+
color: var(--green);
|
|
573
|
+
}
|
|
574
|
+
.plan-status-completed .plan-content {
|
|
575
|
+
color: var(--text-dim);
|
|
576
|
+
}
|
|
577
|
+
.plan-status-unknown {
|
|
578
|
+
color: var(--text-dim);
|
|
579
|
+
}
|
|
508
580
|
|
|
509
581
|
/* Permission dialog */
|
|
510
582
|
.permission {
|
|
@@ -678,7 +750,25 @@ body {
|
|
|
678
750
|
#status-bar::before {
|
|
679
751
|
content: "\200b";
|
|
680
752
|
}
|
|
753
|
+
#status-bar .status-model {
|
|
754
|
+
flex: 0 0 auto;
|
|
755
|
+
}
|
|
756
|
+
#status-bar .status-separator,
|
|
757
|
+
#status-bar .status-usage {
|
|
758
|
+
flex: 0 0 auto;
|
|
759
|
+
}
|
|
681
760
|
#status-bar .status-cwd {
|
|
761
|
+
display: flex;
|
|
762
|
+
flex: 1 1 auto;
|
|
763
|
+
min-width: 0;
|
|
764
|
+
overflow: hidden;
|
|
765
|
+
}
|
|
766
|
+
#status-bar .status-cwd-prefix {
|
|
767
|
+
flex: 0 0 auto;
|
|
768
|
+
}
|
|
769
|
+
#status-bar .status-cwd-tail {
|
|
770
|
+
flex: 1 1 auto;
|
|
771
|
+
min-width: 0;
|
|
682
772
|
overflow: hidden;
|
|
683
773
|
text-overflow: ellipsis;
|
|
684
774
|
direction: rtl;
|
|
@@ -795,6 +885,30 @@ body {
|
|
|
795
885
|
}
|
|
796
886
|
}
|
|
797
887
|
|
|
888
|
+
/* Panels attached to the input */
|
|
889
|
+
.input-panel {
|
|
890
|
+
background: var(--surface);
|
|
891
|
+
border: 1px solid var(--border);
|
|
892
|
+
border-bottom: none;
|
|
893
|
+
max-height: 220px;
|
|
894
|
+
}
|
|
895
|
+
#plan-panel {
|
|
896
|
+
flex: 0 0 auto;
|
|
897
|
+
}
|
|
898
|
+
#plan-panel[hidden] {
|
|
899
|
+
display: none;
|
|
900
|
+
}
|
|
901
|
+
#plan-panel > .plan-summary {
|
|
902
|
+
margin: 0;
|
|
903
|
+
padding: 6px 16px;
|
|
904
|
+
}
|
|
905
|
+
#plan-panel > .plan-entries {
|
|
906
|
+
max-height: 180px;
|
|
907
|
+
margin: 0;
|
|
908
|
+
padding: 0 16px 6px;
|
|
909
|
+
overflow-y: auto;
|
|
910
|
+
}
|
|
911
|
+
|
|
798
912
|
/* Slash command autocomplete */
|
|
799
913
|
#slash-menu {
|
|
800
914
|
display: none;
|
|
@@ -802,10 +916,6 @@ body {
|
|
|
802
916
|
bottom: 100%;
|
|
803
917
|
left: 0;
|
|
804
918
|
right: 0;
|
|
805
|
-
background: var(--surface);
|
|
806
|
-
border: 1px solid var(--border);
|
|
807
|
-
border-bottom: none;
|
|
808
|
-
max-height: 220px;
|
|
809
919
|
overflow-y: auto;
|
|
810
920
|
z-index: 10;
|
|
811
921
|
}
|
|
@@ -900,6 +1010,151 @@ body {
|
|
|
900
1010
|
cursor: default;
|
|
901
1011
|
}
|
|
902
1012
|
|
|
1013
|
+
/* Read-only file viewer */
|
|
1014
|
+
#file-viewer[hidden] {
|
|
1015
|
+
display: none;
|
|
1016
|
+
}
|
|
1017
|
+
#file-viewer {
|
|
1018
|
+
position: fixed;
|
|
1019
|
+
inset: 0;
|
|
1020
|
+
z-index: 100;
|
|
1021
|
+
min-width: 0;
|
|
1022
|
+
background: var(--bg);
|
|
1023
|
+
color: var(--text);
|
|
1024
|
+
display: flex;
|
|
1025
|
+
flex-direction: column;
|
|
1026
|
+
}
|
|
1027
|
+
body.file-viewer-open {
|
|
1028
|
+
overflow: hidden;
|
|
1029
|
+
}
|
|
1030
|
+
.file-viewer-header {
|
|
1031
|
+
min-height: 41px;
|
|
1032
|
+
padding: 6px 16px;
|
|
1033
|
+
background: var(--surface);
|
|
1034
|
+
border-bottom: 1px solid var(--border);
|
|
1035
|
+
display: flex;
|
|
1036
|
+
align-items: center;
|
|
1037
|
+
gap: 12px;
|
|
1038
|
+
font-size: 15px;
|
|
1039
|
+
}
|
|
1040
|
+
#file-viewer-path {
|
|
1041
|
+
min-width: 0;
|
|
1042
|
+
flex: 1;
|
|
1043
|
+
overflow: hidden;
|
|
1044
|
+
text-overflow: ellipsis;
|
|
1045
|
+
white-space: nowrap;
|
|
1046
|
+
direction: ltr;
|
|
1047
|
+
text-align: left;
|
|
1048
|
+
color: var(--text-dim);
|
|
1049
|
+
font-size: inherit;
|
|
1050
|
+
}
|
|
1051
|
+
#file-viewer-close {
|
|
1052
|
+
flex: 0 0 auto;
|
|
1053
|
+
}
|
|
1054
|
+
#file-viewer-notice {
|
|
1055
|
+
flex: 0 0 auto;
|
|
1056
|
+
padding: 6px 14px;
|
|
1057
|
+
border-bottom: 1px solid var(--border);
|
|
1058
|
+
color: var(--yellow);
|
|
1059
|
+
font-size: 13px;
|
|
1060
|
+
}
|
|
1061
|
+
#file-viewer-notice[hidden] {
|
|
1062
|
+
display: none;
|
|
1063
|
+
}
|
|
1064
|
+
#file-viewer-content {
|
|
1065
|
+
flex: 1 1 auto;
|
|
1066
|
+
min-height: 0;
|
|
1067
|
+
overflow: auto;
|
|
1068
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
1069
|
+
}
|
|
1070
|
+
.file-viewer-markdown {
|
|
1071
|
+
width: 100%;
|
|
1072
|
+
min-height: 100%;
|
|
1073
|
+
}
|
|
1074
|
+
.file-viewer-image {
|
|
1075
|
+
display: block;
|
|
1076
|
+
max-width: 100%;
|
|
1077
|
+
max-height: 100%;
|
|
1078
|
+
margin: auto;
|
|
1079
|
+
padding: 12px;
|
|
1080
|
+
object-fit: contain;
|
|
1081
|
+
}
|
|
1082
|
+
.file-viewer-code-scroll {
|
|
1083
|
+
min-height: 100%;
|
|
1084
|
+
min-width: max-content;
|
|
1085
|
+
}
|
|
1086
|
+
.file-viewer-code-grid {
|
|
1087
|
+
display: grid;
|
|
1088
|
+
grid-template-columns: max-content minmax(max-content, 1fr);
|
|
1089
|
+
width: max-content;
|
|
1090
|
+
min-width: 100%;
|
|
1091
|
+
min-height: 100%;
|
|
1092
|
+
}
|
|
1093
|
+
.file-viewer-line-numbers,
|
|
1094
|
+
.file-viewer-code {
|
|
1095
|
+
margin: 0;
|
|
1096
|
+
padding: 10px 12px;
|
|
1097
|
+
border: 0;
|
|
1098
|
+
border-radius: 0;
|
|
1099
|
+
background: var(--bg);
|
|
1100
|
+
color: var(--text);
|
|
1101
|
+
font:
|
|
1102
|
+
14px/1.55 "SF Mono",
|
|
1103
|
+
"Fira Code",
|
|
1104
|
+
monospace;
|
|
1105
|
+
white-space: pre;
|
|
1106
|
+
}
|
|
1107
|
+
.file-viewer-line-numbers {
|
|
1108
|
+
position: sticky;
|
|
1109
|
+
left: 0;
|
|
1110
|
+
z-index: 1;
|
|
1111
|
+
padding-right: 8px;
|
|
1112
|
+
background: var(--surface);
|
|
1113
|
+
border-right: 1px solid var(--border);
|
|
1114
|
+
color: var(--text-dim);
|
|
1115
|
+
text-align: right;
|
|
1116
|
+
user-select: none;
|
|
1117
|
+
}
|
|
1118
|
+
.file-viewer-code {
|
|
1119
|
+
padding-left: 12px;
|
|
1120
|
+
}
|
|
1121
|
+
.file-viewer-code code,
|
|
1122
|
+
.file-viewer-code code.hljs {
|
|
1123
|
+
padding: 0;
|
|
1124
|
+
background: transparent;
|
|
1125
|
+
font: inherit;
|
|
1126
|
+
}
|
|
1127
|
+
.file-viewer-binary-meta {
|
|
1128
|
+
padding: 18px 14px 10px;
|
|
1129
|
+
color: var(--text-dim);
|
|
1130
|
+
overflow-wrap: anywhere;
|
|
1131
|
+
}
|
|
1132
|
+
.file-viewer-download {
|
|
1133
|
+
display: inline-flex;
|
|
1134
|
+
min-height: 44px;
|
|
1135
|
+
margin: 0 14px;
|
|
1136
|
+
padding: 8px 12px;
|
|
1137
|
+
border: 1px solid var(--border);
|
|
1138
|
+
border-radius: var(--radius);
|
|
1139
|
+
align-items: center;
|
|
1140
|
+
color: var(--accent);
|
|
1141
|
+
text-decoration: none;
|
|
1142
|
+
}
|
|
1143
|
+
.file-viewer-download:hover,
|
|
1144
|
+
.file-viewer-download:focus-visible {
|
|
1145
|
+
border-color: var(--accent);
|
|
1146
|
+
}
|
|
1147
|
+
@media (min-width: 1024px) {
|
|
1148
|
+
body.file-viewer-open {
|
|
1149
|
+
padding-right: min(55vw, 800px);
|
|
1150
|
+
}
|
|
1151
|
+
#file-viewer {
|
|
1152
|
+
left: auto;
|
|
1153
|
+
width: min(55vw, 800px);
|
|
1154
|
+
border-left: 1px solid var(--border);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
903
1158
|
/* Image attach */
|
|
904
1159
|
#attach-preview {
|
|
905
1160
|
display: none;
|
package/dist/sw.js
CHANGED
|
@@ -79,25 +79,35 @@ self.addEventListener("notificationclick", (e) => {
|
|
|
79
79
|
e.notification.close();
|
|
80
80
|
|
|
81
81
|
const data = e.notification.data || {};
|
|
82
|
-
// For inbox messages, route to the bound session (if set) or root.
|
|
83
|
-
// For session events, route to the session.
|
|
84
82
|
const sessionId = data.sessionId;
|
|
85
|
-
const
|
|
83
|
+
const messageId = data.messageId;
|
|
84
|
+
const target = sessionId
|
|
85
|
+
? { type: "navigate", sessionId }
|
|
86
|
+
: messageId
|
|
87
|
+
? { type: "navigate", messageId }
|
|
88
|
+
: { type: "navigate" };
|
|
89
|
+
const targetUrl = sessionId
|
|
90
|
+
? `/#${encodeURIComponent(sessionId)}`
|
|
91
|
+
: messageId
|
|
92
|
+
? `/?message=${encodeURIComponent(messageId)}`
|
|
93
|
+
: "/";
|
|
86
94
|
|
|
87
95
|
e.waitUntil(
|
|
88
96
|
self.clients
|
|
89
97
|
.matchAll({ type: "window", includeUncontrolled: true })
|
|
90
|
-
.then((clients) => {
|
|
91
|
-
// Focus existing window if open
|
|
98
|
+
.then(async (clients) => {
|
|
92
99
|
for (const client of clients) {
|
|
93
100
|
if (client.url.includes(self.location.origin)) {
|
|
94
|
-
|
|
95
|
-
|
|
101
|
+
try {
|
|
102
|
+
await client.focus();
|
|
103
|
+
} catch {
|
|
104
|
+
// Navigation still works if the browser refuses focus.
|
|
105
|
+
}
|
|
106
|
+
client.postMessage(target);
|
|
96
107
|
return;
|
|
97
108
|
}
|
|
98
109
|
}
|
|
99
|
-
|
|
100
|
-
return self.clients.openWindow(urlHash);
|
|
110
|
+
return self.clients.openWindow(targetUrl);
|
|
101
111
|
}),
|
|
102
112
|
);
|
|
103
113
|
});
|
package/lib/agent-key.js
ADDED
package/lib/attachments.js
CHANGED
|
@@ -70,14 +70,51 @@ function looksLikeUtf8Text(buf) {
|
|
|
70
70
|
if (byte === 0)
|
|
71
71
|
return false;
|
|
72
72
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
return decodesAsUtf8ToleratingTailTruncation(buf);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Maximum bytes one UTF-8 code point can occupy. A TRAILING code point that
|
|
77
|
+
* was cut off by the sampling window contributes at most one byte less than
|
|
78
|
+
* a full character (a complete 4-byte character would decode), so backing up
|
|
79
|
+
* `MAX_UTF8_CHAR_BYTES - 1` bytes always reaches the preceding boundary.
|
|
80
|
+
*/
|
|
81
|
+
const MAX_UTF8_CHAR_BYTES = 4;
|
|
82
|
+
/**
|
|
83
|
+
* Whether `buf` decodes cleanly as UTF-8, allowing its FINAL multi-byte
|
|
84
|
+
* character to be cut off at the buffer end.
|
|
85
|
+
*
|
|
86
|
+
* Why the tolerance: sniffMime only receives the first 4096 bytes of a
|
|
87
|
+
* larger file (the viewer's `SNIFF_BYTES` window in src/files/routes.ts and
|
|
88
|
+
* the upload-pipeline head). Content dense in multi-byte characters —
|
|
89
|
+
* Chinese, Japanese, emoji, ... — regularly leaves that window slicing
|
|
90
|
+
* clean through a character. A strict whole-buffer fatal decode then
|
|
91
|
+
* misclassifies perfectly valid UTF-8 text as binary, so the viewer
|
|
92
|
+
* force-downloads it instead of previewing it (real-world regression:
|
|
93
|
+
* ~100 of the markdown notes under ~/mine/space downloaded instead of
|
|
94
|
+
* rendering, even though every one of them was valid UTF-8).
|
|
95
|
+
*
|
|
96
|
+
* This relaxes ONLY the tail: a truncated trailing code point occupies at
|
|
97
|
+
* most 3 bytes of the buffer, so backing up at most 3 bytes reaches a
|
|
98
|
+
* character boundary — and the loop never trims the whole buffer, because an
|
|
99
|
+
* empty prefix would trivially "decode" and would turn short garbage buffers
|
|
100
|
+
* into text. NUL bytes and genuinely invalid sequences elsewhere fail every
|
|
101
|
+
* prefix attempt and still fall through to application/octet-stream — the
|
|
102
|
+
* "binary stays binary" security posture of the sniff is unchanged.
|
|
103
|
+
*/
|
|
104
|
+
function decodesAsUtf8ToleratingTailTruncation(buf) {
|
|
105
|
+
for (let end = buf.length; end >= Math.max(1, buf.length - (MAX_UTF8_CHAR_BYTES - 1)); end--) {
|
|
106
|
+
try {
|
|
107
|
+
// Fresh decoder per attempt: reuse-after-throw is not part of the
|
|
108
|
+
// TextDecoder contract across implementations.
|
|
109
|
+
new TextDecoder("utf-8", { fatal: true }).decode(buf.subarray(0, end));
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Cut deeper into the tail. Only a trailing truncated character can
|
|
114
|
+
// make a later attempt succeed — real corruption fails all of them.
|
|
115
|
+
}
|
|
80
116
|
}
|
|
117
|
+
return false;
|
|
81
118
|
}
|
|
82
119
|
/**
|
|
83
120
|
* Mimes we are willing to render inline in <img>. Everything else is forced
|
package/lib/auth-middleware.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// All whitelisted paths must be GETs that read non-sensitive data,
|
|
2
|
-
// assets needed before the user can present a token
|
|
1
|
+
// All whitelisted paths must be GETs that either read non-sensitive data,
|
|
2
|
+
// serve static assets needed before the user can present a token, or enforce
|
|
3
|
+
// independent ticket/HMAC authentication inside their route handler.
|
|
3
4
|
const WHITELIST = [
|
|
4
5
|
// Public probes
|
|
5
6
|
{ method: "GET", test: (p) => p === "/api/v1/version" },
|
|
@@ -35,6 +36,12 @@ const WHITELIST = [
|
|
|
35
36
|
method: "GET",
|
|
36
37
|
test: (p) => /^\/api\/v1\/sessions\/[A-Za-z0-9_-]+\/attachments\/[A-Za-z0-9._-]+$/.test(p),
|
|
37
38
|
},
|
|
39
|
+
// File viewer content — read-only bytes for one confirmed path,
|
|
40
|
+
// authenticated via HMAC sig+exp query string for the same reason as
|
|
41
|
+
// image GETs above (media tags / downloads cannot send Authorization
|
|
42
|
+
// headers). The content handler verifies the signature before serving:
|
|
43
|
+
// without a valid sig+exp it returns 401, never bytes.
|
|
44
|
+
{ method: "GET", test: (p) => p === "/api/v1/files/content" },
|
|
38
45
|
// --- Share viewer (public read-only snapshots) ---
|
|
39
46
|
// Viewer HTML shell + image proxy + viewer-namespaced static assets
|
|
40
47
|
// (CSS/JS) all live under /s/* — see src/share/routes.ts. The auth gate
|