clay-server 4.0.0 → 4.0.1-beta.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/lib/project-sessions.js +15 -5
- package/lib/project.js +6 -0
- package/lib/public/app.js +1 -0
- package/lib/public/css/command-palette.css +26 -0
- package/lib/public/index.html +2 -2
- package/lib/public/modules/app-message-router.js +1 -1
- package/lib/public/modules/search-clay-chat.js +47 -6
- package/lib/public/modules/search-clay-controls.js +86 -0
- package/lib/public/style.css +1 -1
- package/lib/sdk-bridge.js +1 -1
- package/lib/server-home-chat-events.js +3 -0
- package/lib/server-home-chat.js +35 -0
- package/lib/ws-schema.js +6 -0
- package/package.json +1 -1
package/lib/project-sessions.js
CHANGED
|
@@ -87,6 +87,13 @@ function attachSessions(ctx) {
|
|
|
87
87
|
var getLinuxUserForSession = ctx.getLinuxUserForSession;
|
|
88
88
|
var ensureProjectAccessForSession = ctx.ensureProjectAccessForSession;
|
|
89
89
|
var getOsUserInfoForWs = ctx.getOsUserInfoForWs;
|
|
90
|
+
function stopSession(session) {
|
|
91
|
+
if (!session || (!session.isProcessing && !session._queryStarting)) return false;
|
|
92
|
+
if (typeof ctx.onHumanPairStop === "function") ctx.onHumanPairStop(session);
|
|
93
|
+
session.taskStopRequested = true;
|
|
94
|
+
if (session.abortController) session.abortController.abort();
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
90
97
|
var hydrateImageRefs = ctx.hydrateImageRefs;
|
|
91
98
|
var onProcessingChanged = ctx.onProcessingChanged;
|
|
92
99
|
var broadcastPresence = ctx.broadcastPresence;
|
|
@@ -1004,11 +1011,7 @@ function attachSessions(ctx) {
|
|
|
1004
1011
|
|
|
1005
1012
|
if (msg.type === "stop") {
|
|
1006
1013
|
var session = getSessionForWs(ws);
|
|
1007
|
-
|
|
1008
|
-
if (typeof ctx.onHumanPairStop === "function") ctx.onHumanPairStop(session);
|
|
1009
|
-
session.taskStopRequested = true;
|
|
1010
|
-
if (session.abortController) session.abortController.abort();
|
|
1011
|
-
}
|
|
1014
|
+
stopSession(session);
|
|
1012
1015
|
return true;
|
|
1013
1016
|
}
|
|
1014
1017
|
|
|
@@ -2071,6 +2074,13 @@ function attachSessions(ctx) {
|
|
|
2071
2074
|
handleSessionsMessage: handleSessionsMessage,
|
|
2072
2075
|
resolveSessionForView: resolveSessionForView,
|
|
2073
2076
|
respondToAskUser: respondToAskUser,
|
|
2077
|
+
respondToHomePermission: function (ws, msg, session) {
|
|
2078
|
+
if (!session || !msg || !session.pendingPermissions || !session.pendingPermissions[msg.requestId]) return false;
|
|
2079
|
+
var indexedSessionId = sm.permissionRequestIndex[msg.requestId];
|
|
2080
|
+
if (indexedSessionId !== session.localId || sm.sessions.get(indexedSessionId) !== session) return false;
|
|
2081
|
+
return handleSessionsMessage(ws, { type: "permission_response", requestId: msg.requestId, decision: msg.decision });
|
|
2082
|
+
},
|
|
2083
|
+
stopHomeSession: stopSession,
|
|
2074
2084
|
};
|
|
2075
2085
|
}
|
|
2076
2086
|
|
package/lib/project.js
CHANGED
|
@@ -1331,6 +1331,10 @@ function createProjectContext(opts) {
|
|
|
1331
1331
|
if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg, slug);
|
|
1332
1332
|
return;
|
|
1333
1333
|
}
|
|
1334
|
+
if (msg.type === "home_mate_stop" || msg.type === "home_mate_permission_response") {
|
|
1335
|
+
if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg, slug);
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1334
1338
|
if (msg.type === "project_assignment_response" || msg.type === "dm_open" || msg.type === "dm_send" || msg.type === "dm_list" || msg.type === "dm_typing" || msg.type === "dm_add_favorite" || msg.type === "dm_remove_favorite" || msg.type === "mate_create" || msg.type === "mate_list" || msg.type === "mate_delete" || msg.type === "mate_update" || msg.type === "mate_readd_builtin" || msg.type === "mate_list_available_builtins" || msg.type === "email_accounts_list" || msg.type === "email_account_add" || msg.type === "email_account_remove" || msg.type === "email_account_test" || msg.type === "home_debates_list" || msg.type === "home_mate_present" || msg.type === "home_mate_open" || msg.type === "home_mate_sessions_list" || msg.type === "home_mate_session_open" || msg.type === "home_mate_send" || msg.type === "home_mate_new_session" || msg.type === "home_mate_debate_plan" || msg.type === "home_debate_proposal_response" || msg.type === "home_mate_creation_plan" || msg.type === "home_mate_creation_proposal_response" || msg.type === "home_mate_close" || msg.type === "home_mate_memory_list" || msg.type === "home_mate_knowledge_list" || msg.type === "home_mate_models_get" || msg.type === "home_mate_model_set" || msg.type === "home_dock_get" || msg.type === "home_dock_set" || msg.type === "home_surface_get" || msg.type === "home_surface_set" || msg.type === "tools_list" || msg.type === "tool_get" || msg.type === "tool_install" || msg.type === "tool_remove" || msg.type === "tool_storage_op" || msg.type === "tool_llm_op" || msg.type === "tool_llm_config_get" || msg.type === "tool_control_response" || msg.type === "tool_source_get" || msg.type === "tool_mate_access_set" || msg.type === "tool_server_control" || msg.type === "tool_frame_url") {
|
|
1335
1339
|
if (typeof opts.onDmMessage === "function") {
|
|
1336
1340
|
opts.onDmMessage(ws, msg, slug);
|
|
@@ -2314,6 +2318,8 @@ function createProjectContext(opts) {
|
|
|
2314
2318
|
handleHomeDebateProposalResponse: function (ws, msg, session) { return _debateProposal.handleHomeMessage(ws, msg, session); },
|
|
2315
2319
|
handleHomeMateCreationProposalResponse: function (ws, msg, session) { return _mateCreationProposal.handleHomeMessage(ws, msg, session); },
|
|
2316
2320
|
handleHomeAskUserResponse: function (msg, session) { return _sessions.respondToAskUser(session, msg); },
|
|
2321
|
+
handleHomePermissionResponse: function (ws, msg, session) { return _sessions.respondToHomePermission(ws, msg, session); },
|
|
2322
|
+
stopHomeSession: function (session) { return _sessions.stopHomeSession(session); },
|
|
2317
2323
|
handleHomeDebateControl: function (ws, msg, session) { return _debate.handleHomeControl(ws, msg, session); },
|
|
2318
2324
|
getVendorModelCatalog: _models.getVendorCatalog,
|
|
2319
2325
|
getVendorModelAvailability: _models.getVendorAvailability,
|
package/lib/public/app.js
CHANGED
|
@@ -294,6 +294,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
294
294
|
savedMainSlug: null,
|
|
295
295
|
connected: false,
|
|
296
296
|
pendingOutboundMessages: [],
|
|
297
|
+
searchClayChatState: null,
|
|
297
298
|
homeChatMateId: null,
|
|
298
299
|
homeChatSessionId: null,
|
|
299
300
|
homeChatSessionModel: null,
|
|
@@ -392,6 +392,32 @@
|
|
|
392
392
|
gap: 12px;
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
+
.search-clay-permission {
|
|
396
|
+
margin: 0 6px;
|
|
397
|
+
padding: 12px;
|
|
398
|
+
border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--border-subtle));
|
|
399
|
+
border-radius: 10px;
|
|
400
|
+
background: color-mix(in srgb, var(--accent) 6%, var(--bg-secondary));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.search-clay-permission strong { display: block; color: var(--text); font-size: 13px; }
|
|
404
|
+
.search-clay-permission p { margin: 5px 0 10px; color: var(--text-muted); font-size: 11px; line-height: 1.45; overflow-wrap: anywhere; }
|
|
405
|
+
.search-clay-permission > div { display: flex; justify-content: flex-end; gap: 7px; }
|
|
406
|
+
.search-clay-permission button {
|
|
407
|
+
min-height: 30px;
|
|
408
|
+
padding: 0 10px;
|
|
409
|
+
border: 1px solid var(--border);
|
|
410
|
+
border-radius: 7px;
|
|
411
|
+
background: var(--bg);
|
|
412
|
+
color: var(--text);
|
|
413
|
+
font: inherit;
|
|
414
|
+
font-size: 11px;
|
|
415
|
+
cursor: pointer;
|
|
416
|
+
}
|
|
417
|
+
.search-clay-permission button.is-primary { border-color: var(--accent); background: var(--accent); color: white; }
|
|
418
|
+
.search-clay-permission button:disabled { opacity: 0.55; cursor: default; }
|
|
419
|
+
.search-clay-permission small { display: block; margin-top: 7px; color: var(--text-muted); font-size: 11px; }
|
|
420
|
+
|
|
395
421
|
.search-clay-message { width: 100%; min-width: 0; }
|
|
396
422
|
.search-clay-transcript .search-clay-message-user {
|
|
397
423
|
display: flex;
|
package/lib/public/index.html
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
(function(){try{var k="clay-theme-vars",v=localStorage.getItem(k),r=document.documentElement;if(v){var o=JSON.parse(v),p;for(p in o)r.style.setProperty(p,o[p]);var vt=localStorage.getItem(k.replace("-vars","-variant"));if(vt==="light"){r.classList.add("light-theme");r.classList.remove("dark-theme")}else{r.classList.add("dark-theme");r.classList.remove("light-theme")}var m=document.querySelector('meta[name="theme-color"]');if(m&&o["--bg"])m.setAttribute("content",o["--bg"])}else{var sl=window.matchMedia&&window.matchMedia("(prefers-color-scheme: light)").matches;if(sl){r.classList.add("light-theme");r.classList.remove("dark-theme")}}}catch(e){}})();
|
|
32
32
|
</script>
|
|
33
33
|
<script>if(window.navigator.standalone||window.matchMedia("(display-mode:standalone)").matches){document.documentElement.classList.add("pwa-standalone")}</script>
|
|
34
|
-
<link rel="stylesheet" href="style.css?v=
|
|
34
|
+
<link rel="stylesheet" href="style.css?v=20260910-ask-clay-controls1">
|
|
35
35
|
<style>
|
|
36
36
|
@media(max-width:768px){
|
|
37
37
|
/* User messages: vertical stack, avatar on top, right-aligned */
|
|
@@ -2337,7 +2337,7 @@
|
|
|
2337
2337
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0/lib/addon-fit.min.js"></script>
|
|
2338
2338
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0/lib/addon-web-links.min.js"></script>
|
|
2339
2339
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@0/lib/addon-webgl.min.js"></script>
|
|
2340
|
-
<script type="module" src="app.js?v=
|
|
2340
|
+
<script type="module" src="app.js?v=20260910-ask-clay-controls1"></script>
|
|
2341
2341
|
<div id="pwa-install-modal" class="pwa-modal hidden">
|
|
2342
2342
|
<div class="pwa-modal-backdrop"></div>
|
|
2343
2343
|
<div class="pwa-modal-card">
|
|
@@ -78,7 +78,7 @@ export function handleHomeProtocolMessage(msg) {
|
|
|
78
78
|
|
|
79
79
|
export function processMessage(msg) {
|
|
80
80
|
var searchHandled = handleSearchClayMessage(msg);
|
|
81
|
-
if (searchHandled && msg.type === "home_clay_activity") return;
|
|
81
|
+
if (searchHandled && (msg.type === "home_clay_activity" || msg.type === "home_mate_stopping" || msg.type.indexOf("home_mate_permission_") === 0)) return;
|
|
82
82
|
if (handleHomeProtocolMessage(msg)) return;
|
|
83
83
|
processAppMessage(msg);
|
|
84
84
|
}
|
|
@@ -6,8 +6,9 @@ import { refreshIcons } from './icons.js';
|
|
|
6
6
|
import { store } from './store.js';
|
|
7
7
|
import { mateAvatarUrl } from './avatar.js';
|
|
8
8
|
import { createAssistantBubble, createUserBubble, finalizeAssistantBubble, renderAssistantBubbleText } from './chat-bubble-renderer.js';
|
|
9
|
+
import { isExactSearchControlMessage, replaceSearchPermissions, applySearchPermissionMessage, createSearchPermissionCard } from './search-clay-controls.js';
|
|
9
10
|
|
|
10
|
-
var state = null;
|
|
11
|
+
var state = store.get('searchClayChatState') || null;
|
|
11
12
|
var hostEl = null;
|
|
12
13
|
var backHandler = null;
|
|
13
14
|
var closeHandler = null;
|
|
@@ -213,6 +214,22 @@ function submit(input, sendButton) {
|
|
|
213
214
|
render();
|
|
214
215
|
}
|
|
215
216
|
|
|
217
|
+
function stopSearchClayChat() {
|
|
218
|
+
if (!state || !state.processing || state.stopping || !state.mateId || !state.sessionId) return false;
|
|
219
|
+
if (!send({ type: "home_mate_stop", mateId: state.mateId, sessionId: state.sessionId, requestId: state.requestId })) return false;
|
|
220
|
+
state.stopping = true;
|
|
221
|
+
render();
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function respondToPermission(permission, decision) {
|
|
226
|
+
if (!state || permission.status !== "pending") return false;
|
|
227
|
+
if (!send({ type: "home_mate_permission_response", mateId: state.mateId, sessionId: state.sessionId, requestId: state.requestId, permissionRequestId: permission.permissionRequestId, decision: decision })) return false;
|
|
228
|
+
permission.status = "answering";
|
|
229
|
+
render();
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
|
|
216
233
|
function expand() {
|
|
217
234
|
if (!state || !state.mateId || !state.sessionId) return;
|
|
218
235
|
var mateId = state.mateId;
|
|
@@ -260,9 +277,9 @@ function render() {
|
|
|
260
277
|
freshSendButton.className = "search-clay-send";
|
|
261
278
|
freshSendButton.setAttribute("aria-label", "Send to Clay");
|
|
262
279
|
freshSendButton.innerHTML = '<i data-lucide="arrow-up" aria-hidden="true"></i>';
|
|
263
|
-
freshInput.addEventListener("input", function () { state.draft = freshInput.value; freshSendButton.disabled = !state.sessionId || !freshInput.value.trim(); });
|
|
280
|
+
freshInput.addEventListener("input", function () { state.draft = freshInput.value; freshSendButton.disabled = state.processing ? (!state.sessionId || state.stopping) : (!state.sessionId || !freshInput.value.trim()); });
|
|
264
281
|
freshInput.addEventListener("keydown", function (event) { if (event.key === "Enter" && !event.shiftKey && !event.isComposing) { event.preventDefault(); submit(freshInput, freshSendButton); } });
|
|
265
|
-
freshSendButton.addEventListener("click", function () { submit(freshInput, freshSendButton); });
|
|
282
|
+
freshSendButton.addEventListener("click", function () { if (state.processing) stopSearchClayChat(); else submit(freshInput, freshSendButton); });
|
|
266
283
|
composer.appendChild(freshInput);
|
|
267
284
|
composer.appendChild(freshSendButton);
|
|
268
285
|
shell.appendChild(composer);
|
|
@@ -271,6 +288,7 @@ function render() {
|
|
|
271
288
|
transcript.innerHTML = "";
|
|
272
289
|
for (var i = 0; i < state.messages.length; i++) transcript.appendChild(renderMessage(state.messages[i], false));
|
|
273
290
|
if (state.activities.length || state.stream) transcript.appendChild(renderMessage({ role: "assistant", text: state.stream, activities: state.activities, activitiesExpanded: state.activityExpanded, toggleActivities: function (expanded) { state.activityExpanded = expanded; } }, true));
|
|
291
|
+
for (var p = 0; p < state.permissions.length; p++) transcript.appendChild(createSearchPermissionCard(state.permissions[p], respondToPermission));
|
|
274
292
|
if (state.processing && !state.stream && !state.activities.length) {
|
|
275
293
|
var progress = progressCopy();
|
|
276
294
|
var pending = document.createElement("div");
|
|
@@ -297,7 +315,9 @@ function render() {
|
|
|
297
315
|
var sendButton = shell.querySelector(".search-clay-send");
|
|
298
316
|
input.placeholder = state.sessionId ? "Message Clay…" : "Starting Clay…";
|
|
299
317
|
input.disabled = !state.sessionId;
|
|
300
|
-
sendButton.disabled = !state.sessionId || !input.value.trim();
|
|
318
|
+
sendButton.disabled = state.processing ? (!state.sessionId || state.stopping) : (!state.sessionId || !input.value.trim());
|
|
319
|
+
sendButton.setAttribute("aria-label", state.processing ? (state.stopping ? "Stopping Clay" : "Stop Clay") : "Send to Clay");
|
|
320
|
+
sendButton.innerHTML = state.processing ? '<i data-lucide="square" aria-hidden="true"></i>' : '<i data-lucide="arrow-up" aria-hidden="true"></i>';
|
|
301
321
|
var expandButton = shell.querySelector('.search-clay-icon-button[aria-label="Open this conversation in Home"]');
|
|
302
322
|
if (expandButton) expandButton.disabled = !state.sessionId;
|
|
303
323
|
refreshIcons();
|
|
@@ -311,7 +331,8 @@ export function startSearchClayChat(query, host, onBack, onClose) {
|
|
|
311
331
|
backHandler = onBack;
|
|
312
332
|
closeHandler = onClose;
|
|
313
333
|
var startedAt = Date.now();
|
|
314
|
-
state = { requestId: requestId(), mateId: null, sessionId: null, messages: [{ role: "user", text: clean }], queuedUsers: [], pendingTurns: 1, activities: [], activityExpanded: false, stream: "", draft: "", processing: true, awaitingCompletion: true, activity: "thinking", step: 0, startedAt: startedAt, updatedAt: startedAt };
|
|
334
|
+
state = { requestId: requestId(), mateId: null, sessionId: null, messages: [{ role: "user", text: clean }], queuedUsers: [], pendingTurns: 1, activities: [], permissions: [], activityExpanded: false, stream: "", draft: "", processing: true, stopping: false, awaitingCompletion: true, activity: "thinking", step: 0, startedAt: startedAt, updatedAt: startedAt, query: clean };
|
|
335
|
+
store.set({ searchClayChatState: state });
|
|
315
336
|
startProgressTimer();
|
|
316
337
|
render();
|
|
317
338
|
if (!send({ type: "home_clay_ask", requestId: state.requestId, text: clean })) {
|
|
@@ -330,6 +351,7 @@ export function attachSearchClayChat(host, onBack, onClose) {
|
|
|
330
351
|
hostEl = host;
|
|
331
352
|
backHandler = onBack;
|
|
332
353
|
closeHandler = onClose;
|
|
354
|
+
if (state.processing) send({ type: "home_clay_ask", requestId: state.requestId, text: state.query });
|
|
333
355
|
render();
|
|
334
356
|
return true;
|
|
335
357
|
}
|
|
@@ -349,7 +371,7 @@ export function getSearchClayChatSummary() {
|
|
|
349
371
|
export function detachSearchClayChat() { hostEl = null; }
|
|
350
372
|
|
|
351
373
|
export function handleSearchClayMessage(msg) {
|
|
352
|
-
if (!state
|
|
374
|
+
if (!isExactSearchControlMessage(state, msg)) return false;
|
|
353
375
|
state.updatedAt = Date.now();
|
|
354
376
|
if (msg.mateId) state.mateId = msg.mateId;
|
|
355
377
|
if (msg.type === "home_mate_session_identity" && msg.sessionId) state.sessionId = msg.sessionId;
|
|
@@ -366,6 +388,12 @@ export function handleSearchClayMessage(msg) {
|
|
|
366
388
|
state.pendingTurns = state.processing ? Math.max(1, state.pendingTurns) : 0;
|
|
367
389
|
if (state.processing) startProgressTimer();
|
|
368
390
|
state.stream = "";
|
|
391
|
+
state.stopping = false;
|
|
392
|
+
replaceSearchPermissions(state, msg.pendingPermissions);
|
|
393
|
+
} else if (msg.type === "home_mate_permission_request" || msg.type === "home_mate_permission_resolved" || msg.type === "home_mate_permission_cancelled") {
|
|
394
|
+
applySearchPermissionMessage(state, msg);
|
|
395
|
+
} else if (msg.type === "home_mate_stopping") {
|
|
396
|
+
state.stopping = true;
|
|
369
397
|
} else if (msg.type === "home_mate_delta") {
|
|
370
398
|
if (!state.awaitingCompletion) return true;
|
|
371
399
|
state.sessionId = msg.sessionId || state.sessionId;
|
|
@@ -381,6 +409,7 @@ export function handleSearchClayMessage(msg) {
|
|
|
381
409
|
state.activityExpanded = false;
|
|
382
410
|
state.pendingTurns = Math.max(0, state.pendingTurns - 1);
|
|
383
411
|
state.processing = state.pendingTurns > 0;
|
|
412
|
+
state.stopping = false;
|
|
384
413
|
state.awaitingCompletion = state.processing;
|
|
385
414
|
state.startedAt = Date.now();
|
|
386
415
|
if (!state.processing) stopProgressTimer();
|
|
@@ -393,6 +422,7 @@ export function handleSearchClayMessage(msg) {
|
|
|
393
422
|
state.pendingTurns = Math.max(0, state.pendingTurns - 1);
|
|
394
423
|
state.activityExpanded = false;
|
|
395
424
|
state.processing = state.pendingTurns > 0;
|
|
425
|
+
state.stopping = false;
|
|
396
426
|
state.awaitingCompletion = state.processing;
|
|
397
427
|
state.startedAt = Date.now();
|
|
398
428
|
if (!state.processing) stopProgressTimer();
|
|
@@ -400,3 +430,14 @@ export function handleSearchClayMessage(msg) {
|
|
|
400
430
|
render();
|
|
401
431
|
return true;
|
|
402
432
|
}
|
|
433
|
+
|
|
434
|
+
store.subscribe(function (appState, previous) {
|
|
435
|
+
if (!state || appState.connected === previous.connected) return;
|
|
436
|
+
if (!appState.connected) {
|
|
437
|
+
replaceSearchPermissions(state, []);
|
|
438
|
+
state.stopping = false;
|
|
439
|
+
render();
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if (state.processing) send({ type: "home_clay_ask", requestId: state.requestId, text: state.query });
|
|
443
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
function cleanText(value, fallback) {
|
|
2
|
+
if (typeof value !== "string") return fallback || "";
|
|
3
|
+
var clean = value.replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim();
|
|
4
|
+
return clean || fallback || "";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isExactSearchControlMessage(state, message) {
|
|
8
|
+
if (!state || !message || message.requestId !== state.requestId) return false;
|
|
9
|
+
if (state.mateId && message.mateId && message.mateId !== state.mateId) return false;
|
|
10
|
+
if (state.sessionId) {
|
|
11
|
+
if (message.type === "home_mate_session_identity") return message.previousSessionId === state.sessionId;
|
|
12
|
+
if (message.sessionId !== state.sessionId) return false;
|
|
13
|
+
}
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function replaceSearchPermissions(state, permissions) {
|
|
18
|
+
state.permissions = Array.isArray(permissions) ? permissions.filter(function (item) {
|
|
19
|
+
return item && typeof item.permissionRequestId === "string" && item.permissionRequestId;
|
|
20
|
+
}).map(function (item) {
|
|
21
|
+
return { permissionRequestId: item.permissionRequestId, toolName: cleanText(item.toolName, "Tool"), toolInput: item.toolInput || {}, decisionReason: cleanText(item.decisionReason, ""), status: "pending" };
|
|
22
|
+
}) : [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function applySearchPermissionMessage(state, message) {
|
|
26
|
+
if (!isExactSearchControlMessage(state, message)) return false;
|
|
27
|
+
if (!Array.isArray(state.permissions)) state.permissions = [];
|
|
28
|
+
var permissionRequestId = message.permissionRequestId;
|
|
29
|
+
var index = -1;
|
|
30
|
+
for (var i = 0; i < state.permissions.length; i++) {
|
|
31
|
+
if (state.permissions[i].permissionRequestId === permissionRequestId) index = i;
|
|
32
|
+
}
|
|
33
|
+
if (message.type === "home_mate_permission_request") {
|
|
34
|
+
var permission = { permissionRequestId: permissionRequestId, toolName: cleanText(message.toolName, "Tool"), toolInput: message.toolInput || {}, decisionReason: cleanText(message.decisionReason, ""), status: "pending" };
|
|
35
|
+
if (index === -1) state.permissions.push(permission);
|
|
36
|
+
else state.permissions[index] = permission;
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (index === -1) return true;
|
|
40
|
+
if (message.type === "home_mate_permission_resolved") {
|
|
41
|
+
state.permissions[index].status = message.decision === "allow" ? "allowed" : "denied";
|
|
42
|
+
} else if (message.type === "home_mate_permission_cancelled") {
|
|
43
|
+
state.permissions[index].status = "cancelled";
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function permissionTarget(permission) {
|
|
49
|
+
var input = permission.toolInput && typeof permission.toolInput === "object" ? permission.toolInput : {};
|
|
50
|
+
return cleanText(input.file_path || input.command || input.url || input.query || input.pattern || permission.decisionReason, "Clay needs your approval to continue.");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createSearchPermissionCard(permission, onDecision) {
|
|
54
|
+
var card = document.createElement("section");
|
|
55
|
+
card.className = "search-clay-permission is-" + permission.status;
|
|
56
|
+
card.setAttribute("role", "group");
|
|
57
|
+
card.setAttribute("aria-label", "Permission request for " + permission.toolName);
|
|
58
|
+
var title = document.createElement("strong");
|
|
59
|
+
title.textContent = "Allow " + permission.toolName + "?";
|
|
60
|
+
card.appendChild(title);
|
|
61
|
+
var target = document.createElement("p");
|
|
62
|
+
target.textContent = permissionTarget(permission);
|
|
63
|
+
card.appendChild(target);
|
|
64
|
+
if (permission.status === "pending" || permission.status === "answering") {
|
|
65
|
+
var actions = document.createElement("div");
|
|
66
|
+
var deny = document.createElement("button");
|
|
67
|
+
deny.type = "button";
|
|
68
|
+
deny.textContent = "Deny";
|
|
69
|
+
deny.disabled = permission.status === "answering";
|
|
70
|
+
deny.addEventListener("click", function () { onDecision(permission, "deny"); });
|
|
71
|
+
var allow = document.createElement("button");
|
|
72
|
+
allow.type = "button";
|
|
73
|
+
allow.className = "is-primary";
|
|
74
|
+
allow.textContent = "Approve";
|
|
75
|
+
allow.disabled = permission.status === "answering";
|
|
76
|
+
allow.addEventListener("click", function () { onDecision(permission, "allow"); });
|
|
77
|
+
actions.appendChild(deny);
|
|
78
|
+
actions.appendChild(allow);
|
|
79
|
+
card.appendChild(actions);
|
|
80
|
+
} else {
|
|
81
|
+
var result = document.createElement("small");
|
|
82
|
+
result.textContent = permission.status === "allowed" ? "Approved" : (permission.status === "denied" ? "Denied" : "Cancelled");
|
|
83
|
+
card.appendChild(result);
|
|
84
|
+
}
|
|
85
|
+
return card;
|
|
86
|
+
}
|
package/lib/public/style.css
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
@import url("css/session-search.css");
|
|
42
42
|
@import url("css/tooltip.css");
|
|
43
43
|
@import url("css/mates.css?v=20260407");
|
|
44
|
-
@import url("css/command-palette.css?v=
|
|
44
|
+
@import url("css/command-palette.css?v=20260910-ask-clay-controls1");
|
|
45
45
|
@import url("css/mention.css");
|
|
46
46
|
@import url("css/debate.css");
|
|
47
47
|
@import url("css/home-debate-planning.css");
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -866,7 +866,7 @@ function createSDKBridge(opts) {
|
|
|
866
866
|
}
|
|
867
867
|
|
|
868
868
|
var _nm = getNotificationsModule();
|
|
869
|
-
if (_nm) {
|
|
869
|
+
if (_nm && session.homeClayEntryMode !== "search") {
|
|
870
870
|
_nm.notify("permission_request", {
|
|
871
871
|
title: permissionPushTitle(toolName, input),
|
|
872
872
|
body: permissionPushBody(toolName, input),
|
|
@@ -267,6 +267,9 @@ function transformEvent(event, mateId, session, requestId, stableSessionId) {
|
|
|
267
267
|
}
|
|
268
268
|
if (event.type === "tool_executing") return Object.assign({ type: "home_clay_activity", activityId: session._homeClayToolActivityId || "tool:" + (session._homeClayActivityStep || 1), phase: "searching", status: "active", label: searchToolLabel(event.name, event.input), step: session._homeClayActivityStep || 1 }, metadata);
|
|
269
269
|
if (event.type === "tool_result") return Object.assign({ type: "home_clay_activity", activityId: session._homeClayToolActivityId || "tool:" + (session._homeClayActivityStep || 1), phase: event.is_error ? "reconsidering" : "reviewing", status: event.is_error ? "error" : "done", label: event.is_error ? "That lead did not work; trying another route" : "Reviewed the search results", step: session._homeClayActivityStep || 1 }, metadata);
|
|
270
|
+
if (event.type === "permission_request") return Object.assign({ type: "home_mate_permission_request", permissionRequestId: event.requestId, toolName: event.toolName, toolInput: event.toolInput, decisionReason: event.decisionReason || "" }, metadata);
|
|
271
|
+
if (event.type === "permission_resolved") return Object.assign({ type: "home_mate_permission_resolved", permissionRequestId: event.requestId, decision: event.decision || "deny" }, metadata);
|
|
272
|
+
if (event.type === "permission_cancel") return Object.assign({ type: "home_mate_permission_cancelled", permissionRequestId: event.requestId }, metadata);
|
|
270
273
|
}
|
|
271
274
|
if (session && session.homeDebatePlanning === true && HOME_DEBATE_EVENT_TYPES[event.type]) {
|
|
272
275
|
var live = { type: "home_debate_event", eventType: event.type };
|
package/lib/server-home-chat.js
CHANGED
|
@@ -224,6 +224,12 @@ function attachHomeChat(deps) {
|
|
|
224
224
|
messages: historyToHomeChat(session.history || [], session.debateSetupMode === true, session.mateCreationMode === true),
|
|
225
225
|
isProcessing: session.isProcessing === true,
|
|
226
226
|
};
|
|
227
|
+
if (session.homeClayEntryMode === "search") {
|
|
228
|
+
payload.pendingPermissions = Object.keys(session.pendingPermissions || {}).map(function (permissionRequestId) {
|
|
229
|
+
var pending = session.pendingPermissions[permissionRequestId];
|
|
230
|
+
return { permissionRequestId: permissionRequestId, toolName: pending.toolName, toolInput: pending.toolInput, decisionReason: pending.decisionReason || "" };
|
|
231
|
+
});
|
|
232
|
+
}
|
|
227
233
|
if (session.debateSetupMode === true) payload.debatePlanning = true;
|
|
228
234
|
if (session.homeDebatePhase) payload.debatePhase = session.homeDebatePhase;
|
|
229
235
|
if (session.mateCreationMode === true) payload.mateCreation = true;
|
|
@@ -388,6 +394,35 @@ function attachHomeChat(deps) {
|
|
|
388
394
|
|
|
389
395
|
function handleMessage(ws, msg) {
|
|
390
396
|
if (!msg || typeof msg.type !== "string") return false;
|
|
397
|
+
if (msg.type === "home_mate_stop" || msg.type === "home_mate_permission_response") {
|
|
398
|
+
var controlUserId = ws._clayUser ? ws._clayUser.id : null;
|
|
399
|
+
if (users.isMultiUser() && !controlUserId) {
|
|
400
|
+
sendError(ws, msg.mateId || null, "Not authenticated.", msg.requestId || null, msg.sessionId || null);
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
var controlFound = findMateProject(controlUserId, msg.mateId, true);
|
|
404
|
+
var controlTap = messageTap(ws, msg);
|
|
405
|
+
var controlManager = controlFound ? controlFound.ctx.getSessionManager() : null;
|
|
406
|
+
var controlSession = controlManager && controlTap && controlTap.mateId === controlFound.mate.id && controlTap.requestId === msg.requestId
|
|
407
|
+
? controlManager.sessions.get(controlTap.sessionId) : null;
|
|
408
|
+
var controlReference = controlSession ? sessionReference(controlSession) : null;
|
|
409
|
+
if (!controlFound || !controlSession || controlReference !== msg.sessionId || !ownsSession(controlSession, controlUserId) || controlSession.homeClayEntryMode !== "search") {
|
|
410
|
+
sendError(ws, msg.mateId || null, "Conversation control is no longer available.", msg.requestId || null, msg.sessionId || null, "session_not_found");
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
if (msg.type === "home_mate_permission_response") {
|
|
414
|
+
var controlDecision = msg.decision === "allow" ? "allow" : (msg.decision === "deny" ? "deny" : null);
|
|
415
|
+
if (!controlDecision || typeof msg.permissionRequestId !== "string" || !controlSession.pendingPermissions[msg.permissionRequestId]) {
|
|
416
|
+
sendError(ws, controlFound.mate.id, "This permission request is no longer pending.", msg.requestId || null, msg.sessionId || null, "permission_not_found", { toolId: msg.permissionRequestId });
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
controlFound.ctx.handleHomePermissionResponse(ws, { requestId: msg.permissionRequestId, decision: controlDecision }, controlSession);
|
|
420
|
+
} else {
|
|
421
|
+
sendMessage(ws, { type: "home_mate_stopping", mateId: controlFound.mate.id, sessionId: controlReference, requestId: msg.requestId || null });
|
|
422
|
+
controlFound.ctx.stopHomeSession(controlSession);
|
|
423
|
+
}
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
391
426
|
if (msg.type !== "home_debates_list" && msg.type !== "home_clay_ask" && msg.type !== "home_clay_session_resolve" && msg.type !== "home_clay_log_resolve" && msg.type !== "home_mate_present" && msg.type !== "home_mate_open" && msg.type !== "home_mate_sessions_list" && msg.type !== "home_mate_session_open" && msg.type !== "home_mate_send" && msg.type !== "home_mate_new_session" && msg.type !== "home_mate_debate_plan" && msg.type !== "home_debate_proposal_response" && msg.type !== "home_debate_question_response" && msg.type !== "home_debate_control" && msg.type !== "home_mate_creation_plan" && msg.type !== "home_mate_creation_question_response" && msg.type !== "home_mate_creation_proposal_response" && msg.type !== "home_mate_close" && msg.type !== "home_mate_memory_list" && msg.type !== "home_mate_knowledge_list" && msg.type !== "home_mate_models_get" && msg.type !== "home_mate_model_set") {
|
|
392
427
|
return false;
|
|
393
428
|
}
|
package/lib/ws-schema.js
CHANGED
|
@@ -643,6 +643,8 @@ var schema = {
|
|
|
643
643
|
"home_mate_sessions_list": { direction: "c2s", handler: "lib/server-home-chat.js", description: "List the current user's conversations with the selected mate" },
|
|
644
644
|
"home_mate_session_open": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Open one explicit conversation with the selected mate" },
|
|
645
645
|
"home_mate_send": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Send a message to the selected mate from the home hub" },
|
|
646
|
+
"home_mate_stop": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Stop the exact query-bound global Ask Clay turn" },
|
|
647
|
+
"home_mate_permission_response": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Answer an exact pending global Ask Clay permission request" },
|
|
646
648
|
"home_mate_new_session": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Start a fresh embedded session with the selected mate, optionally with a typed creation intent" },
|
|
647
649
|
"home_mate_debate_plan": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Start a fresh server-seeded debate planning conversation with Clay using an optional prefilled topic" },
|
|
648
650
|
"home_mate_creation_plan": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Start a fresh server-seeded Mate creation interview with Clay" },
|
|
@@ -659,6 +661,10 @@ var schema = {
|
|
|
659
661
|
"home_mate_model_set": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Validate and persist the Mate vendor/model, optionally applying it to an exact owned pristine Home draft" },
|
|
660
662
|
"home_mate_history": { direction: "s2c", handler: "lib/public/modules/home-mate-chat.js", description: "Selected mate's initial or refreshed chat history" },
|
|
661
663
|
"home_clay_activity": { direction: "s2c", handler: "lib/public/modules/search-clay-chat.js", description: "Sanitized progress stage for a query-bound global-search Clay conversation" },
|
|
664
|
+
"home_mate_permission_request": { direction: "s2c", handler: "lib/public/modules/search-clay-chat.js", description: "Exact pending permission card for a global Ask Clay conversation" },
|
|
665
|
+
"home_mate_permission_resolved": { direction: "s2c", handler: "lib/public/modules/search-clay-chat.js", description: "Server-confirmed Ask Clay permission decision" },
|
|
666
|
+
"home_mate_permission_cancelled": { direction: "s2c", handler: "lib/public/modules/search-clay-chat.js", description: "Server-confirmed Ask Clay permission cancellation" },
|
|
667
|
+
"home_mate_stopping": { direction: "s2c", handler: "lib/public/modules/search-clay-chat.js", description: "Server accepted Stop for the exact Ask Clay conversation" },
|
|
662
668
|
"home_mate_session_identity": { direction: "s2c", handler: "lib/public/modules/app-message-router.js", description: "Promote an active Home session from its local reference to its durable runtime identity" },
|
|
663
669
|
"home_mate_sessions_state": { direction: "s2c", handler: "lib/public/modules/home-mate-chat.js", description: "Owned visible Mate conversations with sanitized runtime identity, model, status, and timestamps" },
|
|
664
670
|
"home_mate_delta": { direction: "s2c", handler: "lib/public/modules/home-mate-chat.js", description: "Selected mate's streaming assistant text delta" },
|
package/package.json
CHANGED