clay-server 2.24.4 → 2.25.0-beta.2
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/lib/pages.js +1 -1
- package/lib/project.js +32 -1
- package/lib/public/app.js +24 -14
- package/lib/public/css/mates.css +4 -4
- package/lib/public/css/mention.css +0 -4
- package/lib/public/css/messages.css +15 -0
- package/lib/public/index.html +2 -2
- package/lib/public/modules/debate.js +3 -1
- package/lib/public/modules/markdown.js +26 -3
- package/lib/public/modules/mention.js +2 -2
- package/lib/public/sw.js +2 -0
- package/lib/push.js +20 -1
- package/lib/sdk-bridge.js +15 -1
- package/lib/server.js +14 -1
- package/package.json +1 -1
package/lib/pages.js
CHANGED
|
@@ -2,7 +2,7 @@ function pinPageHtml() {
|
|
|
2
2
|
return '<!DOCTYPE html><html lang="en"><head>' +
|
|
3
3
|
'<meta charset="UTF-8">' +
|
|
4
4
|
'<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">' +
|
|
5
|
-
'<meta name="
|
|
5
|
+
'<meta name="mobile-web-app-capable" content="yes">' +
|
|
6
6
|
'<link rel="icon" type="image/png" href="/favicon-banded.png">' +
|
|
7
7
|
'<link rel="apple-touch-icon" href="/apple-touch-icon.png">' +
|
|
8
8
|
'<title>Clay</title>' +
|
package/lib/project.js
CHANGED
|
@@ -90,6 +90,18 @@ function safePath(base, requested) {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// Resolve an absolute path without requiring it to be within cwd.
|
|
94
|
+
// Used as fallback in OS user mode where ACL enforces access at the OS level.
|
|
95
|
+
function safeAbsPath(requested) {
|
|
96
|
+
if (!requested) return null;
|
|
97
|
+
var resolved = path.resolve(requested);
|
|
98
|
+
try {
|
|
99
|
+
return fs.realpathSync(resolved);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
93
105
|
/**
|
|
94
106
|
* Create a project context — per-project state and handlers.
|
|
95
107
|
* opts: { cwd, slug, title, pushModule, debug, dangerouslySkipPermissions, currentVersion }
|
|
@@ -1758,7 +1770,8 @@ function createProjectContext(opts) {
|
|
|
1758
1770
|
}
|
|
1759
1771
|
|
|
1760
1772
|
if (msg.type === "push_subscribe") {
|
|
1761
|
-
|
|
1773
|
+
var _pushUserId = ws._clayUser ? ws._clayUser.id : null;
|
|
1774
|
+
if (pushModule && msg.subscription) pushModule.addSubscription(msg.subscription, msg.replaceEndpoint, _pushUserId);
|
|
1762
1775
|
return;
|
|
1763
1776
|
}
|
|
1764
1777
|
|
|
@@ -2832,6 +2845,10 @@ function createProjectContext(opts) {
|
|
|
2832
2845
|
}
|
|
2833
2846
|
if (msg.type === "fs_list") {
|
|
2834
2847
|
var fsDir = safePath(cwd, msg.path || ".");
|
|
2848
|
+
// In OS user mode, fall back to absolute path resolution (ACL enforces access)
|
|
2849
|
+
if (!fsDir && getOsUserInfoForWs(ws)) {
|
|
2850
|
+
fsDir = safeAbsPath(msg.path);
|
|
2851
|
+
}
|
|
2835
2852
|
if (!fsDir) {
|
|
2836
2853
|
sendTo(ws, { type: "fs_list_result", path: msg.path, entries: [], error: "Access denied" });
|
|
2837
2854
|
return;
|
|
@@ -2874,6 +2891,9 @@ function createProjectContext(opts) {
|
|
|
2874
2891
|
|
|
2875
2892
|
if (msg.type === "fs_read") {
|
|
2876
2893
|
var fsFile = safePath(cwd, msg.path);
|
|
2894
|
+
if (!fsFile && getOsUserInfoForWs(ws)) {
|
|
2895
|
+
fsFile = safeAbsPath(msg.path);
|
|
2896
|
+
}
|
|
2877
2897
|
if (!fsFile) {
|
|
2878
2898
|
sendTo(ws, { type: "fs_read_result", path: msg.path, error: "Access denied" });
|
|
2879
2899
|
return;
|
|
@@ -2920,6 +2940,9 @@ function createProjectContext(opts) {
|
|
|
2920
2940
|
// --- File write ---
|
|
2921
2941
|
if (msg.type === "fs_write") {
|
|
2922
2942
|
var fsWriteFile = safePath(cwd, msg.path);
|
|
2943
|
+
if (!fsWriteFile && getOsUserInfoForWs(ws)) {
|
|
2944
|
+
fsWriteFile = safeAbsPath(msg.path);
|
|
2945
|
+
}
|
|
2923
2946
|
if (!fsWriteFile) {
|
|
2924
2947
|
sendTo(ws, { type: "fs_write_result", path: msg.path, ok: false, error: "Access denied" });
|
|
2925
2948
|
return;
|
|
@@ -3749,6 +3772,11 @@ function createProjectContext(opts) {
|
|
|
3749
3772
|
// Keep any pending scheduled message alive when user sends a regular message
|
|
3750
3773
|
|
|
3751
3774
|
var userMsg = { type: "user_message", text: msg.text || "" };
|
|
3775
|
+
// Attach sender info for multi-user attribution (backward-compatible: old clients ignore these)
|
|
3776
|
+
if (ws._clayUser) {
|
|
3777
|
+
userMsg.from = ws._clayUser.id;
|
|
3778
|
+
userMsg.fromName = ws._clayUser.displayName || ws._clayUser.username || "";
|
|
3779
|
+
}
|
|
3752
3780
|
var savedImagePaths = [];
|
|
3753
3781
|
if (msg.images && msg.images.length > 0) {
|
|
3754
3782
|
userMsg.imageCount = msg.images.length;
|
|
@@ -5361,6 +5389,9 @@ function createProjectContext(opts) {
|
|
|
5361
5389
|
var reqFilePath = params.get("path");
|
|
5362
5390
|
if (!reqFilePath) { res.writeHead(400); res.end("Missing path"); return true; }
|
|
5363
5391
|
var absFile = safePath(cwd, reqFilePath);
|
|
5392
|
+
if (!absFile && getOsUserInfoForReq(req)) {
|
|
5393
|
+
absFile = safeAbsPath(reqFilePath);
|
|
5394
|
+
}
|
|
5364
5395
|
if (!absFile) { res.writeHead(403); res.end("Access denied"); return true; }
|
|
5365
5396
|
var fileExt = path.extname(absFile).toLowerCase();
|
|
5366
5397
|
if (!IMAGE_EXTS.has(fileExt)) { res.writeHead(403); res.end("Only image files"); return true; }
|
package/lib/public/app.js
CHANGED
|
@@ -2865,10 +2865,11 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2865
2865
|
// AskUserQuestion, PermissionRequest, Plan, Todo, Thinking, Tool items -> modules/tools.js
|
|
2866
2866
|
|
|
2867
2867
|
// --- DOM: Messages ---
|
|
2868
|
-
function addUserMessage(text, images, pastes) {
|
|
2868
|
+
function addUserMessage(text, images, pastes, fromUserId, fromUserName) {
|
|
2869
2869
|
if (!text && (!images || images.length === 0) && (!pastes || pastes.length === 0)) return;
|
|
2870
|
+
var isOtherUser = fromUserId && fromUserId !== myUserId;
|
|
2870
2871
|
var div = document.createElement("div");
|
|
2871
|
-
div.className = "msg-user";
|
|
2872
|
+
div.className = "msg-user" + (isOtherUser ? " msg-user-other" : "");
|
|
2872
2873
|
div.dataset.turn = ++turnCounter;
|
|
2873
2874
|
var bubble = document.createElement("div");
|
|
2874
2875
|
bubble.className = "bubble";
|
|
@@ -2926,14 +2927,27 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2926
2927
|
|
|
2927
2928
|
|
|
2928
2929
|
// Always render avatar + header structure (CSS controls visibility)
|
|
2929
|
-
var
|
|
2930
|
-
|
|
2931
|
-
|
|
2930
|
+
var _targetUser;
|
|
2931
|
+
var _displayName;
|
|
2932
|
+
if (isOtherUser) {
|
|
2933
|
+
_targetUser = cachedAllUsers.find(function (u) { return u.id === fromUserId; });
|
|
2934
|
+
_displayName = fromUserName || (_targetUser && (_targetUser.displayName || _targetUser.username)) || "User";
|
|
2935
|
+
} else {
|
|
2936
|
+
_targetUser = cachedAllUsers.find(function (u) { return u.id === myUserId; });
|
|
2937
|
+
if (!_targetUser) {
|
|
2938
|
+
try { _targetUser = JSON.parse(localStorage.getItem("clay_my_user") || "null"); } catch(e) {}
|
|
2939
|
+
}
|
|
2940
|
+
_displayName = document.body.dataset.myDisplayName || "";
|
|
2941
|
+
if (!_displayName) {
|
|
2942
|
+
_displayName = (_targetUser && (_targetUser.displayName || _targetUser.username)) || "Me";
|
|
2943
|
+
}
|
|
2932
2944
|
}
|
|
2933
2945
|
|
|
2934
2946
|
var avi = document.createElement("img");
|
|
2935
|
-
avi.className = "dm-bubble-avatar dm-bubble-avatar-me";
|
|
2936
|
-
avi.src =
|
|
2947
|
+
avi.className = "dm-bubble-avatar" + (isOtherUser ? " dm-bubble-avatar-other" : " dm-bubble-avatar-me");
|
|
2948
|
+
avi.src = isOtherUser
|
|
2949
|
+
? userAvatarUrl(_targetUser || { id: fromUserId }, 36)
|
|
2950
|
+
: (document.body.dataset.myAvatarUrl || userAvatarUrl(_targetUser || { id: myUserId }, 36));
|
|
2937
2951
|
div.appendChild(avi);
|
|
2938
2952
|
|
|
2939
2953
|
var contentWrap = document.createElement("div");
|
|
@@ -2941,13 +2955,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
2941
2955
|
|
|
2942
2956
|
var header = document.createElement("div");
|
|
2943
2957
|
header.className = "dm-bubble-header";
|
|
2944
|
-
var myDisplayName = document.body.dataset.myDisplayName || "";
|
|
2945
|
-
if (!myDisplayName) {
|
|
2946
|
-
myDisplayName = (_myU && (_myU.displayName || _myU.username)) || "Me";
|
|
2947
|
-
}
|
|
2948
2958
|
var nameSpan = document.createElement("span");
|
|
2949
2959
|
nameSpan.className = "dm-bubble-name";
|
|
2950
|
-
nameSpan.textContent =
|
|
2960
|
+
nameSpan.textContent = _displayName;
|
|
2951
2961
|
header.appendChild(nameSpan);
|
|
2952
2962
|
var timeSpan = document.createElement("span");
|
|
2953
2963
|
timeSpan.className = "dm-bubble-time";
|
|
@@ -4408,9 +4418,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4408
4418
|
if (msg.planContent) {
|
|
4409
4419
|
setPlanContent(msg.planContent);
|
|
4410
4420
|
renderPlanCard(msg.planContent);
|
|
4411
|
-
addUserMessage("Execute the following plan. Do NOT re-enter plan mode — just implement it step by step.", msg.images || null, msg.pastes || null);
|
|
4421
|
+
addUserMessage("Execute the following plan. Do NOT re-enter plan mode — just implement it step by step.", msg.images || null, msg.pastes || null, msg.from, msg.fromName);
|
|
4412
4422
|
} else {
|
|
4413
|
-
addUserMessage(msg.text, msg.images || null, msg.pastes || null);
|
|
4423
|
+
addUserMessage(msg.text, msg.images || null, msg.pastes || null, msg.from, msg.fromName);
|
|
4414
4424
|
}
|
|
4415
4425
|
break;
|
|
4416
4426
|
|
package/lib/public/css/mates.css
CHANGED
|
@@ -732,7 +732,7 @@ body.mate-dm-active #layout.sidebar-collapsed .mate-collapsed-info {
|
|
|
732
732
|
right: 12px;
|
|
733
733
|
margin-top: 4px;
|
|
734
734
|
padding: 10px 14px;
|
|
735
|
-
background: var(--bg
|
|
735
|
+
background: var(--bg);
|
|
736
736
|
border: 1px solid var(--border-subtle);
|
|
737
737
|
border-radius: 8px;
|
|
738
738
|
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
|
|
@@ -823,7 +823,7 @@ body.mate-dm-active #layout.sidebar-collapsed .mate-collapsed-info {
|
|
|
823
823
|
justify-content: center;
|
|
824
824
|
}
|
|
825
825
|
.mate-confirm-box {
|
|
826
|
-
background: var(--bg
|
|
826
|
+
background: var(--bg);
|
|
827
827
|
border: 1px solid var(--border-subtle);
|
|
828
828
|
border-radius: 12px;
|
|
829
829
|
padding: 20px 24px;
|
|
@@ -846,7 +846,7 @@ body.mate-dm-active #layout.sidebar-collapsed .mate-collapsed-info {
|
|
|
846
846
|
border: 1px solid var(--border-subtle);
|
|
847
847
|
font-size: 13px;
|
|
848
848
|
cursor: pointer;
|
|
849
|
-
background: var(--bg
|
|
849
|
+
background: var(--bg);
|
|
850
850
|
color: var(--text);
|
|
851
851
|
}
|
|
852
852
|
.mate-confirm-delete {
|
|
@@ -873,7 +873,7 @@ body.mate-dm-active #layout.sidebar-collapsed .mate-collapsed-info {
|
|
|
873
873
|
background: rgba(0,0,0,0.45);
|
|
874
874
|
}
|
|
875
875
|
.mate-onboarding-card {
|
|
876
|
-
background: var(--bg
|
|
876
|
+
background: var(--bg);
|
|
877
877
|
border: 1px solid var(--border-subtle);
|
|
878
878
|
border-radius: 16px;
|
|
879
879
|
padding: 32px 36px;
|
|
@@ -150,6 +150,21 @@
|
|
|
150
150
|
text-align: start;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
/* Other user's message: left-aligned with flipped bubble */
|
|
154
|
+
.msg-user-other {
|
|
155
|
+
align-items: flex-start;
|
|
156
|
+
}
|
|
157
|
+
.msg-user-other .bubble {
|
|
158
|
+
background: var(--bg-elevated, var(--bg-hover));
|
|
159
|
+
border-radius: 20px 20px 20px 4px;
|
|
160
|
+
}
|
|
161
|
+
.msg-user-other .dm-bubble-avatar-other {
|
|
162
|
+
display: block;
|
|
163
|
+
}
|
|
164
|
+
.msg-user-other .dm-bubble-header {
|
|
165
|
+
display: flex;
|
|
166
|
+
}
|
|
167
|
+
|
|
153
168
|
/* --- User message action bar --- */
|
|
154
169
|
.msg-actions {
|
|
155
170
|
display: flex;
|
package/lib/public/index.html
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
|
6
|
-
<meta name="
|
|
6
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
7
7
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
8
8
|
<script>
|
|
9
9
|
(function(){
|
|
10
10
|
var isIOS=/iPad|iPhone|iPod/.test(navigator.userAgent)||(navigator.platform==="MacIntel"&&navigator.maxTouchPoints>1);
|
|
11
|
-
if(!isIOS){var l=document.createElement("link");l.rel="manifest";l.href="manifest.json";document.head.appendChild(l)}
|
|
11
|
+
if(!isIOS){var l=document.createElement("link");l.rel="manifest";l.href="/manifest.json";document.head.appendChild(l)}
|
|
12
12
|
})();
|
|
13
13
|
</script>
|
|
14
14
|
<meta name="theme-color" content="#282a36">
|
|
@@ -546,7 +546,9 @@ function exportDebateAsPdf() {
|
|
|
546
546
|
popup.document.close();
|
|
547
547
|
|
|
548
548
|
popup.onload = function () {
|
|
549
|
-
popup.document.fonts.ready.
|
|
549
|
+
var fontReady = popup.document.fonts ? popup.document.fonts.ready : Promise.resolve();
|
|
550
|
+
var timeout = new Promise(function (r) { setTimeout(r, 3000); });
|
|
551
|
+
Promise.race([fontReady, timeout]).then(function () {
|
|
550
552
|
popup.focus();
|
|
551
553
|
popup.print();
|
|
552
554
|
if (typeof popup.onafterprint !== "undefined") {
|
|
@@ -187,7 +187,22 @@ export function svgToPngDataUrl(svgEl) {
|
|
|
187
187
|
var svgBlob = new Blob([svgStr], { type: "image/svg+xml;charset=utf-8" });
|
|
188
188
|
var url = URL.createObjectURL(svgBlob);
|
|
189
189
|
|
|
190
|
+
// Inline external styles into SVG to avoid tainted canvas from cross-origin refs
|
|
191
|
+
var styleSheets = "";
|
|
192
|
+
try {
|
|
193
|
+
var computed = svgEl.querySelectorAll("style");
|
|
194
|
+
for (var si = 0; si < computed.length; si++) {
|
|
195
|
+
styleSheets += computed[si].textContent;
|
|
196
|
+
}
|
|
197
|
+
} catch (e) {}
|
|
198
|
+
// Strip any external references (url(), @import) that would taint the canvas
|
|
199
|
+
svgStr = svgStr.replace(/@import\s+url\([^)]*\)[^;]*;?/g, "");
|
|
200
|
+
svgBlob = new Blob([svgStr], { type: "image/svg+xml;charset=utf-8" });
|
|
201
|
+
URL.revokeObjectURL(url);
|
|
202
|
+
url = URL.createObjectURL(svgBlob);
|
|
203
|
+
|
|
190
204
|
var img = new Image();
|
|
205
|
+
img.crossOrigin = "anonymous";
|
|
191
206
|
img.onload = function () {
|
|
192
207
|
var canvas = document.createElement("canvas");
|
|
193
208
|
canvas.width = w;
|
|
@@ -197,7 +212,12 @@ export function svgToPngDataUrl(svgEl) {
|
|
|
197
212
|
ctx.fillRect(0, 0, w, h);
|
|
198
213
|
ctx.drawImage(img, 0, 0, w, h);
|
|
199
214
|
URL.revokeObjectURL(url);
|
|
200
|
-
|
|
215
|
+
try {
|
|
216
|
+
resolve(canvas.toDataURL("image/png"));
|
|
217
|
+
} catch (e) {
|
|
218
|
+
// Tainted canvas fallback: return original SVG as data URL
|
|
219
|
+
resolve("data:image/svg+xml;charset=utf-8," + encodeURIComponent(svgStr));
|
|
220
|
+
}
|
|
201
221
|
};
|
|
202
222
|
img.onerror = function () {
|
|
203
223
|
URL.revokeObjectURL(url);
|
|
@@ -276,8 +296,11 @@ export function exportMarkdownAsPdf(markdownEl, filename) {
|
|
|
276
296
|
popup.document.close();
|
|
277
297
|
|
|
278
298
|
popup.onload = function () {
|
|
279
|
-
// Wait for web fonts (Pretendard, Roboto Mono) before printing
|
|
280
|
-
|
|
299
|
+
// Wait for web fonts (Pretendard, Roboto Mono) before printing.
|
|
300
|
+
// Timeout after 3s so PDF export still works when CDN fonts are unreachable.
|
|
301
|
+
var fontReady = popup.document.fonts ? popup.document.fonts.ready : Promise.resolve();
|
|
302
|
+
var timeout = new Promise(function (resolve) { setTimeout(resolve, 3000); });
|
|
303
|
+
Promise.race([fontReady, timeout]).then(function () {
|
|
281
304
|
popup.focus();
|
|
282
305
|
popup.print();
|
|
283
306
|
if (typeof popup.onafterprint !== "undefined") {
|
|
@@ -335,7 +335,7 @@ export function handleMentionStart(msg) {
|
|
|
335
335
|
var stopBtn = document.createElement("button");
|
|
336
336
|
stopBtn.className = "mention-stop-btn";
|
|
337
337
|
stopBtn.title = "Stop";
|
|
338
|
-
stopBtn.
|
|
338
|
+
stopBtn.textContent = "Stop";
|
|
339
339
|
stopBtn.addEventListener("click", function () {
|
|
340
340
|
if (ctx.ws && ctx.connected) {
|
|
341
341
|
ctx.ws.send(JSON.stringify({ type: "mention_stop", mateId: msg.mateId }));
|
|
@@ -383,7 +383,7 @@ export function handleMentionStart(msg) {
|
|
|
383
383
|
var stopBtn = document.createElement("button");
|
|
384
384
|
stopBtn.className = "mention-stop-btn";
|
|
385
385
|
stopBtn.title = "Stop";
|
|
386
|
-
stopBtn.
|
|
386
|
+
stopBtn.textContent = "Stop";
|
|
387
387
|
stopBtn.addEventListener("click", function () {
|
|
388
388
|
if (ctx.ws && ctx.connected) {
|
|
389
389
|
ctx.ws.send(JSON.stringify({ type: "mention_stop", mateId: msg.mateId }));
|
package/lib/public/sw.js
CHANGED
|
@@ -110,6 +110,8 @@ self.addEventListener("push", function (event) {
|
|
|
110
110
|
} else if (data.type === "error") {
|
|
111
111
|
options.requireInteraction = true;
|
|
112
112
|
options.tag = "claude-error";
|
|
113
|
+
} else if (data.type === "dm") {
|
|
114
|
+
options.tag = data.tag || "dm";
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
event.waitUntil(
|
package/lib/push.js
CHANGED
|
@@ -80,12 +80,14 @@ function initPush() {
|
|
|
80
80
|
})(startupEndpoints[si]);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
function addSubscription(sub, replaceEndpoint) {
|
|
83
|
+
function addSubscription(sub, replaceEndpoint, userId) {
|
|
84
84
|
if (!sub || !sub.endpoint) return;
|
|
85
85
|
// Remove previous subscription from the same client if endpoint changed
|
|
86
86
|
if (replaceEndpoint && replaceEndpoint !== sub.endpoint) {
|
|
87
87
|
subscriptions.delete(replaceEndpoint);
|
|
88
88
|
}
|
|
89
|
+
// Attach userId for per-user targeting (backward-compatible: old subs without userId still work for broadcast)
|
|
90
|
+
if (userId) sub._userId = userId;
|
|
89
91
|
// Store immediately, then validate async. Invalid subs get cleaned on first sendPush.
|
|
90
92
|
subscriptions.set(sub.endpoint, sub);
|
|
91
93
|
save();
|
|
@@ -119,11 +121,28 @@ function initPush() {
|
|
|
119
121
|
});
|
|
120
122
|
}
|
|
121
123
|
|
|
124
|
+
function sendPushToUser(userId, payload) {
|
|
125
|
+
if (!userId) return;
|
|
126
|
+
var json = JSON.stringify(payload);
|
|
127
|
+
subscriptions.forEach(function (sub, endpoint) {
|
|
128
|
+
if (sub._userId !== userId) return;
|
|
129
|
+
webpush.sendNotification(sub, json, { vapidDetails: vapidDetails })
|
|
130
|
+
.then(function () {})
|
|
131
|
+
.catch(function (err) {
|
|
132
|
+
if (err.statusCode === 410 || err.statusCode === 404 || err.statusCode === 403) {
|
|
133
|
+
subscriptions.delete(endpoint);
|
|
134
|
+
save();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
122
140
|
return {
|
|
123
141
|
publicKey: keys.publicKey,
|
|
124
142
|
addSubscription: addSubscription,
|
|
125
143
|
removeSubscription: removeSubscription,
|
|
126
144
|
sendPush: sendPush,
|
|
145
|
+
sendPushToUser: sendPushToUser,
|
|
127
146
|
};
|
|
128
147
|
}
|
|
129
148
|
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -955,6 +955,7 @@ function createSDKBridge(opts) {
|
|
|
955
955
|
|
|
956
956
|
function cleanupWorker(worker) {
|
|
957
957
|
console.log("[sdk-bridge] cleanupWorker() called, pid=" + (worker.process ? worker.process.pid : "?") + " stack=" + new Error().stack.split("\n").slice(1, 4).join(" | "));
|
|
958
|
+
if (worker._abortTimeout) { clearTimeout(worker._abortTimeout); worker._abortTimeout = null; }
|
|
958
959
|
if (worker.connection && !worker.connection.destroyed) {
|
|
959
960
|
try { worker.connection.end(); } catch (e) {}
|
|
960
961
|
}
|
|
@@ -1069,7 +1070,20 @@ function createSDKBridge(opts) {
|
|
|
1069
1070
|
session.pendingElicitations = {};
|
|
1070
1071
|
session.streamedText = false;
|
|
1071
1072
|
session.responsePreview = "";
|
|
1072
|
-
session.abortController = { abort: function() {
|
|
1073
|
+
session.abortController = { abort: function() {
|
|
1074
|
+
console.log("[sdk-bridge] ABORT sent to worker pid=" + (worker.process ? worker.process.pid : "?"));
|
|
1075
|
+
worker._abortSent = true;
|
|
1076
|
+
try { worker.send({ type: "abort" }); } catch (e) {}
|
|
1077
|
+
// If the worker doesn't finish within 5s (e.g. subagent stuck), force-kill it.
|
|
1078
|
+
// The worker exit handler will dispatch a fallback query_error and send done.
|
|
1079
|
+
if (worker._abortTimeout) clearTimeout(worker._abortTimeout);
|
|
1080
|
+
worker._abortTimeout = setTimeout(function() {
|
|
1081
|
+
if (worker.process && !worker.process.killed && session.isProcessing) {
|
|
1082
|
+
console.log("[sdk-bridge] Abort timeout: force-killing worker pid=" + (worker.process ? worker.process.pid : "?"));
|
|
1083
|
+
try { worker.process.kill("SIGKILL"); } catch (e) {}
|
|
1084
|
+
}
|
|
1085
|
+
}, 5000);
|
|
1086
|
+
} };
|
|
1073
1087
|
|
|
1074
1088
|
// Build initial user message content
|
|
1075
1089
|
var content = [];
|
package/lib/server.js
CHANGED
|
@@ -1072,7 +1072,8 @@ function createServer(opts) {
|
|
|
1072
1072
|
try {
|
|
1073
1073
|
var parsed = JSON.parse(body);
|
|
1074
1074
|
var sub = parsed.subscription || parsed;
|
|
1075
|
-
|
|
1075
|
+
var _httpPushUser = getMultiUserFromReq(req);
|
|
1076
|
+
pushModule.addSubscription(sub, parsed.replaceEndpoint, _httpPushUser ? _httpPushUser.id : null);
|
|
1076
1077
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1077
1078
|
res.end('{"ok":true}');
|
|
1078
1079
|
} catch (e) {
|
|
@@ -3161,6 +3162,18 @@ function createServer(opts) {
|
|
|
3161
3162
|
otherWs.send(JSON.stringify({ type: "dm_message", dmKey: msg.dmKey, message: message }));
|
|
3162
3163
|
});
|
|
3163
3164
|
});
|
|
3165
|
+
// Send push notification to target user
|
|
3166
|
+
if (pushModule && pushModule.sendPushToUser) {
|
|
3167
|
+
var senderName = ws._clayUser ? (ws._clayUser.displayName || ws._clayUser.username || "Someone") : "Someone";
|
|
3168
|
+
var preview = (msg.text || "").substring(0, 140);
|
|
3169
|
+
pushModule.sendPushToUser(targetId, {
|
|
3170
|
+
type: "dm",
|
|
3171
|
+
title: senderName,
|
|
3172
|
+
body: preview,
|
|
3173
|
+
tag: "dm-" + msg.dmKey,
|
|
3174
|
+
dmKey: msg.dmKey,
|
|
3175
|
+
});
|
|
3176
|
+
}
|
|
3164
3177
|
return;
|
|
3165
3178
|
}
|
|
3166
3179
|
|