clay-server 5.2.0-beta.1 → 5.2.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/project-connection.js +1 -1
- package/lib/project-sessions.js +3 -1
- package/lib/public/app.js +2 -0
- package/lib/public/css/command-palette.css +12 -33
- package/lib/public/modules/app-header.js +3 -2
- package/lib/public/modules/app-messages.js +12 -4
- package/lib/public/modules/app-rendering.js +4 -3
- package/lib/public/modules/chat-render-runtime.js +8 -0
- package/lib/public/modules/clay-log-links.js +23 -0
- package/lib/public/modules/command-palette.js +94 -5
- package/lib/public/modules/markdown.js +1 -1
- package/lib/server-home-chat.js +3 -3
- package/lib/server-home-clay-session-links.js +17 -9
- package/lib/server.js +1 -1
- package/lib/sessions.js +3 -1
- package/lib/workspace-query-service.js +13 -2
- package/package.json +1 -1
|
@@ -301,7 +301,7 @@ function attachConnection(ctx) {
|
|
|
301
301
|
}
|
|
302
302
|
sendTo(ws, { type: "history_meta", total: total, from: fromIndex });
|
|
303
303
|
for (var i = fromIndex; i < total; i++) {
|
|
304
|
-
sendTo(ws, hydrateImageRefs(active.history[i]));
|
|
304
|
+
sendTo(ws, Object.assign({}, hydrateImageRefs(active.history[i]), { _historyIndex: i }));
|
|
305
305
|
}
|
|
306
306
|
var _lastUsage = null, _lastModelUsage = null, _lastCost = null, _lastStreamInputTokens = null;
|
|
307
307
|
for (var _ri = total - 1; _ri >= 0; _ri--) {
|
package/lib/project-sessions.js
CHANGED
|
@@ -588,7 +588,9 @@ function attachSessions(ctx) {
|
|
|
588
588
|
var targetFrom = typeof msg.target === "number" ? msg.target : before - sm.HISTORY_PAGE_SIZE;
|
|
589
589
|
var from = sm.findTurnBoundary(session.history, Math.max(0, targetFrom));
|
|
590
590
|
var to = before;
|
|
591
|
-
var items = session.history.slice(from, to).map(
|
|
591
|
+
var items = session.history.slice(from, to).map(function (item, index) {
|
|
592
|
+
return Object.assign({}, hydrateImageRefs(item), { _historyIndex: from + index });
|
|
593
|
+
});
|
|
592
594
|
sendTo(ws, {
|
|
593
595
|
type: "history_prepend",
|
|
594
596
|
items: items,
|
package/lib/public/app.js
CHANGED
|
@@ -315,6 +315,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
315
315
|
historyTotal: 0,
|
|
316
316
|
replayingHistory: false,
|
|
317
317
|
historyReplayBatch: null,
|
|
318
|
+
pendingSessionEntryNavigation: null,
|
|
319
|
+
sessionNavigationRequestId: null,
|
|
318
320
|
projectName: projectName,
|
|
319
321
|
cwd: "",
|
|
320
322
|
currentSlug: currentSlug,
|
|
@@ -550,41 +550,20 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
|
|
|
550
550
|
.clayos-issue-link { --record-color: var(--link, var(--accent)); }
|
|
551
551
|
.clayos-log-link { --record-color: var(--accent); }
|
|
552
552
|
|
|
553
|
-
.clayos-session-link {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
border-radius: 7px;
|
|
562
|
-
background: color-mix(in srgb, var(--accent) 7%, var(--bg-alt));
|
|
563
|
-
color: var(--text);
|
|
564
|
-
font: inherit;
|
|
565
|
-
font-size: 11px;
|
|
566
|
-
line-height: 1;
|
|
567
|
-
vertical-align: middle;
|
|
568
|
-
cursor: pointer;
|
|
553
|
+
.clayos-session-link { --record-color: var(--accent); }
|
|
554
|
+
|
|
555
|
+
#messages .session-entry-target {
|
|
556
|
+
outline: 2px solid color-mix(in srgb, var(--accent) 72%, transparent);
|
|
557
|
+
outline-offset: 3px;
|
|
558
|
+
border-radius: 8px;
|
|
559
|
+
background: color-mix(in srgb, var(--accent) 7%, transparent);
|
|
560
|
+
animation: sessionEntryTargetFade 1800ms ease-out forwards;
|
|
569
561
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
width: 17px;
|
|
575
|
-
height: 17px;
|
|
576
|
-
border-radius: 5px;
|
|
577
|
-
background: color-mix(in srgb, var(--accent) 14%, transparent);
|
|
578
|
-
color: var(--accent);
|
|
579
|
-
font-size: 12px;
|
|
562
|
+
|
|
563
|
+
@keyframes sessionEntryTargetFade {
|
|
564
|
+
0%, 72% { background: color-mix(in srgb, var(--accent) 10%, transparent); }
|
|
565
|
+
100% { background: transparent; }
|
|
580
566
|
}
|
|
581
|
-
.clayos-session-link > span { font-weight: 620; }
|
|
582
|
-
.clayos-session-link > small { color: var(--text-muted); font-size: 10px; }
|
|
583
|
-
.clayos-session-link:hover { border-color: color-mix(in srgb, var(--accent) 52%, var(--border)); background: color-mix(in srgb, var(--accent) 11%, var(--bg-alt)); }
|
|
584
|
-
.clayos-session-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
585
|
-
.clayos-session-link.is-loading { opacity: 0.62; cursor: progress; }
|
|
586
|
-
.clayos-session-link.is-loading::before { animation: searchClaySpin 1.2s linear infinite; }
|
|
587
|
-
.clayos-session-link.is-error { border-color: color-mix(in srgb, var(--warning) 48%, var(--border)); }
|
|
588
567
|
|
|
589
568
|
.search-clay-pending {
|
|
590
569
|
align-self: flex-start;
|
|
@@ -350,6 +350,7 @@ export function prependOlderHistory(items, meta) {
|
|
|
350
350
|
|
|
351
351
|
// Save current rendering state
|
|
352
352
|
var savedMsgEl = store.get('currentMsgEl');
|
|
353
|
+
var savedReplayingHistory = store.get('replayingHistory');
|
|
353
354
|
var savedActivity = getActivityEl();
|
|
354
355
|
var savedFullText = store.get('currentFullText');
|
|
355
356
|
var savedTurnCounter = getTurnCounter();
|
|
@@ -359,7 +360,7 @@ export function prependOlderHistory(items, meta) {
|
|
|
359
360
|
var savedUsage = JSON.parse(JSON.stringify(getSessionUsage()));
|
|
360
361
|
|
|
361
362
|
// Reset to initial values for clean rendering
|
|
362
|
-
store.set({ currentMsgEl: null, currentFullText: "" });
|
|
363
|
+
store.set({ currentMsgEl: null, currentFullText: "", replayingHistory: true });
|
|
363
364
|
setActivityEl(null);
|
|
364
365
|
setTurnCounter(0);
|
|
365
366
|
resetToolState();
|
|
@@ -387,7 +388,7 @@ export function prependOlderHistory(items, meta) {
|
|
|
387
388
|
setPrependAnchor(null);
|
|
388
389
|
|
|
389
390
|
// Restore saved state
|
|
390
|
-
store.set({ currentMsgEl: savedMsgEl, currentFullText: savedFullText });
|
|
391
|
+
store.set({ currentMsgEl: savedMsgEl, currentFullText: savedFullText, replayingHistory: savedReplayingHistory });
|
|
391
392
|
setActivityEl(savedActivity);
|
|
392
393
|
setTurnCounter(savedTurnCounter);
|
|
393
394
|
restoreToolState(savedToolsState);
|
|
@@ -40,7 +40,7 @@ import { handleHomeMateMemoryState, handleHomeMateKnowledgeState, syncHomeMateSe
|
|
|
40
40
|
import { renderKnowledgeList, handleKnowledgeContent, handleKnowledgeMutation } from './mate-knowledge.js';
|
|
41
41
|
import { handleKnowledgeWorkbenchMessage } from './mate-knowledge-workbench.js';
|
|
42
42
|
import { renderMemoryList } from './mate-memory.js';
|
|
43
|
-
import { handlePaletteSessionSwitch, setPaletteVersion } from './command-palette.js';
|
|
43
|
+
import { handlePaletteSessionSwitch, handlePaletteHistoryDone, handlePaletteHistoryPrepended, setPaletteVersion } from './command-palette.js';
|
|
44
44
|
import { handleFindInSessionResults } from './session-search.js';
|
|
45
45
|
import { syncPaneTitles, maybeRestoreSplitGroup, openGroup } from './split-view.js';
|
|
46
46
|
import { showPairDialog, handlePairCreated, handleSplitDelegation, showWorkerDelegationNotice, hideWorkerDelegationNotice } from './split-pair-ui.js';
|
|
@@ -224,6 +224,7 @@ export function processMessage(msg) {
|
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
store.set({ currentHistoryIndex: Number.isSafeInteger(msg._historyIndex) && msg._historyIndex >= 0 ? msg._historyIndex : null });
|
|
227
228
|
switch (msg.type) {
|
|
228
229
|
case "message_ack":
|
|
229
230
|
var acknowledgedEntry = acknowledgeMessage(msg.clientMessageId);
|
|
@@ -250,6 +251,7 @@ export function processMessage(msg) {
|
|
|
250
251
|
|
|
251
252
|
case "history_prepend":
|
|
252
253
|
prependOlderHistory(msg.items, msg.meta);
|
|
254
|
+
handlePaletteHistoryPrepended();
|
|
253
255
|
break;
|
|
254
256
|
|
|
255
257
|
case "history_done":
|
|
@@ -311,11 +313,16 @@ export function processMessage(msg) {
|
|
|
311
313
|
// re-pins on every height change while armed. Disarms early on
|
|
312
314
|
// any real user scroll input.
|
|
313
315
|
// Skip arming when we have a pending in-conversation navigate
|
|
314
|
-
// target (file-edit deeplink) — the navigate block
|
|
315
|
-
// that element into view, and sticky-bottom would
|
|
316
|
+
// target (file-edit or Session-entry deeplink) — the navigate block
|
|
317
|
+
// below scrolls that element into view, and sticky-bottom would
|
|
318
|
+
// fight it.
|
|
316
319
|
var nav = getPendingNavigate();
|
|
317
320
|
var hasNavTarget = nav && (nav.toolId || nav.assistantUuid);
|
|
318
|
-
|
|
321
|
+
var sessionEntryNav = store.get('pendingSessionEntryNavigation');
|
|
322
|
+
var hasSessionEntryNav = sessionEntryNav
|
|
323
|
+
&& String(sessionEntryNav.projectSlug) === String(store.get('currentSlug'))
|
|
324
|
+
&& String(sessionEntryNav.sessionId) === String(store.get('activeSessionId'));
|
|
325
|
+
if (hasNavTarget || hasSessionEntryNav) {
|
|
319
326
|
// Navigate block below will scrollIntoView on the target — don't
|
|
320
327
|
// arm sticky-bottom or it would fight that scroll.
|
|
321
328
|
scrollToBottom();
|
|
@@ -343,6 +350,7 @@ export function processMessage(msg) {
|
|
|
343
350
|
}
|
|
344
351
|
});
|
|
345
352
|
}
|
|
353
|
+
requestAnimationFrame(function () { handlePaletteHistoryDone(); });
|
|
346
354
|
break;
|
|
347
355
|
|
|
348
356
|
case "restore_mate_dm":
|
|
@@ -8,10 +8,10 @@ import { refreshIcons } from './icons.js';
|
|
|
8
8
|
import { closeToolGroup } from './tools.js';
|
|
9
9
|
import { sendMessage, hasSendableContent } from './input.js';
|
|
10
10
|
import { getChatLayout } from './theme.js';
|
|
11
|
-
import { addToMessages, scrollToBottom, forceScrollToBottom, getMsgTime, shouldGroupMessage, getTurnCounter, getActivityEl } from './chat-render-runtime.js';
|
|
11
|
+
import { addToMessages, addHistoryIndex, scrollToBottom, forceScrollToBottom, getMsgTime, shouldGroupMessage, getTurnCounter, getActivityEl } from './chat-render-runtime.js';
|
|
12
12
|
import { resolveProjectAssistantIdentity, applyProjectAssistantIdentity } from './project-chat-identity.js';
|
|
13
13
|
export { VENDOR_ORDER, EXPERIMENTAL_VENDORS, isExperimentalVendor } from './vendor-priority.js';
|
|
14
|
-
export { initRendering, addToMessages, scrollToBottom, forceScrollToBottom, getMsgTime, shouldGroupMessage, getTurnCounter, setTurnCounter, getPrependAnchor, setPrependAnchor, getActivityEl, setActivityEl, getIsUserScrolledUp, setIsUserScrolledUp, getStickyBottom, armStickyBottom, disarmStickyBottom, VENDOR_AVATARS, VENDOR_NAMES, VENDOR_HOMEPAGES } from './chat-render-runtime.js';
|
|
14
|
+
export { initRendering, addToMessages, addHistoryIndex, scrollToBottom, forceScrollToBottom, getMsgTime, shouldGroupMessage, getTurnCounter, setTurnCounter, getPrependAnchor, setPrependAnchor, getActivityEl, setActivityEl, getIsUserScrolledUp, setIsUserScrolledUp, getStickyBottom, armStickyBottom, disarmStickyBottom, VENDOR_AVATARS, VENDOR_NAMES, VENDOR_HOMEPAGES } from './chat-render-runtime.js';
|
|
15
15
|
export { addUserMessage, addSystemMessage, addConflictMessage, addContextOverflowMessage } from './app-message-cards.js';
|
|
16
16
|
|
|
17
17
|
// --- Module-owned state (not in store) ---
|
|
@@ -44,7 +44,8 @@ export function addCopyHandler(msgEl, rawText) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export function appendDelta(text) {
|
|
47
|
-
ensureAssistantBlock();
|
|
47
|
+
var assistantEl = ensureAssistantBlock();
|
|
48
|
+
if (store.get('replayingHistory')) addHistoryIndex(assistantEl, store.get('currentHistoryIndex'));
|
|
48
49
|
streamBuffer += text;
|
|
49
50
|
if (store.get('replayingHistory')) {
|
|
50
51
|
store.set({ currentFullText: store.get('currentFullText') + streamBuffer });
|
|
@@ -155,11 +155,19 @@ export function setIsUserScrolledUp(value) { isUserScrolledUp = value; }
|
|
|
155
155
|
|
|
156
156
|
export function addToMessages(element) {
|
|
157
157
|
var messagesEl = getMessagesEl();
|
|
158
|
+
addHistoryIndex(element, store.get('currentHistoryIndex'));
|
|
158
159
|
if (prependAnchor) messagesEl.insertBefore(element, prependAnchor);
|
|
159
160
|
else messagesEl.appendChild(element);
|
|
160
161
|
keepActiveThinkingAtTail();
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
export function addHistoryIndex(element, historyIndex) {
|
|
165
|
+
if (!element || !Number.isSafeInteger(historyIndex) || historyIndex < 0) return;
|
|
166
|
+
var existing = element.dataset.historyIndexes || "";
|
|
167
|
+
if (existing.split(",").indexOf(String(historyIndex)) !== -1) return;
|
|
168
|
+
element.dataset.historyIndexes = existing ? existing + "," + historyIndex : String(historyIndex);
|
|
169
|
+
}
|
|
170
|
+
|
|
163
171
|
// Thinking is a single accumulating record for the active turn. Other live
|
|
164
172
|
// transcript entries may arrive between reasoning segments, so keep only the
|
|
165
173
|
// currently active record after the tail activity indicator.
|
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
import { enhanceClayIssueLinks, isExactClayIssueReference } from './clay-issue-links.js';
|
|
2
2
|
import { enhanceGithubLinks } from './github-links.js';
|
|
3
|
+
import { escapeHtml } from './utils.js';
|
|
3
4
|
|
|
4
5
|
export function renderClayRecordLink(href, text) {
|
|
6
|
+
if (isExactClaySessionReference(href)) return renderClaySessionLink(href.trim(), text);
|
|
5
7
|
if (!isExactClayLogReference(href) && !isExactClayIssueReference(href)) return null;
|
|
6
8
|
return '<a href="#" data-clay-record-ref="' + href.trim() + '">' + text + '</a>';
|
|
7
9
|
}
|
|
10
|
+
|
|
11
|
+
export function isExactClaySessionReference(text) {
|
|
12
|
+
return parseClaySessionReference(text) !== null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function renderClaySessionLink(ref, text) {
|
|
16
|
+
var parsed = parseClaySessionReference(ref);
|
|
17
|
+
if (!parsed) return null;
|
|
18
|
+
var label = text ? String(text).trim() : "";
|
|
19
|
+
var safeLabel = escapeHtml(label);
|
|
20
|
+
var entryAttr = parsed.entryIndex === null ? "" : ' data-session-entry-index="' + parsed.entryIndex + '"';
|
|
21
|
+
return '<button type="button" class="clayos-session-link clayos-record-link" data-session-ref="' + parsed.ref + '"' + entryAttr + ' aria-label="Open session' + (safeLabel ? " from " + safeLabel : "") + '"><span>Session</span>' + (safeLabel ? '<small>' + safeLabel + '</small>' : '') + '</button>';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function parseClaySessionReference(text) {
|
|
25
|
+
var match = /^((?:session:[A-Za-z0-9_-]{24}))(?:#entry-([0-9]+))?$/.exec((text || "").trim());
|
|
26
|
+
if (!match) return null;
|
|
27
|
+
var entryIndex = match[2] === undefined ? null : Number(match[2]);
|
|
28
|
+
if (entryIndex !== null && (!Number.isSafeInteger(entryIndex) || entryIndex < 0)) return null;
|
|
29
|
+
return { ref: match[1], entryIndex: entryIndex };
|
|
30
|
+
}
|
|
8
31
|
export function parseClayLogReferences(text) {
|
|
9
32
|
var pattern = /\[clayos\/(log:[A-Za-z0-9_-]{24})(?:\s+[\u2014-]\s+([^\]\n]{1,80}))?\]|(^|[^A-Za-z0-9_/:])(log:[A-Za-z0-9_-]{24})(?![A-Za-z0-9_-])/g;
|
|
10
33
|
var results = [];
|
|
@@ -5,6 +5,8 @@ import { openSearch as openSessionSearch } from './session-search.js';
|
|
|
5
5
|
import { startSearchClayChat, attachSearchClayChat, detachSearchClayChat, getSearchClayChatSummary } from './search-clay-chat.js';
|
|
6
6
|
import { getWs } from './ws-ref.js';
|
|
7
7
|
import { openProjectLog } from './project-logs.js';
|
|
8
|
+
import { store } from './store.js';
|
|
9
|
+
import { disarmStickyBottom } from './chat-render-runtime.js';
|
|
8
10
|
|
|
9
11
|
function formatRelativeDate(ts) {
|
|
10
12
|
if (!ts) return "";
|
|
@@ -70,6 +72,10 @@ function handleClaySessionLinkClick(event) {
|
|
|
70
72
|
var requestId = "clayos-" + (isLog ? "log-" : "session-") + Date.now() + "-" + Math.random().toString(36).slice(2, 9);
|
|
71
73
|
if (isLog) link.dataset.logRequest = requestId;
|
|
72
74
|
else link.dataset.sessionRequest = requestId;
|
|
75
|
+
if (!isLog) store.set({ sessionNavigationRequestId: requestId, sessionNavigationRequestContext: {
|
|
76
|
+
projectSlug: store.get('currentSlug'),
|
|
77
|
+
sessionId: store.get('activeSessionId')
|
|
78
|
+
} });
|
|
73
79
|
link.classList.remove("is-error");
|
|
74
80
|
link.classList.add("is-loading");
|
|
75
81
|
var request = { requestId: requestId, surface: link.closest(".search-clay-chat") ? "search" : "home" };
|
|
@@ -79,6 +85,7 @@ function handleClaySessionLinkClick(event) {
|
|
|
79
85
|
} else {
|
|
80
86
|
request.type = "home_clay_session_resolve";
|
|
81
87
|
request.sessionRef = link.dataset.sessionRef;
|
|
88
|
+
if (link.dataset.sessionEntryIndex !== undefined) request.entryIndex = Number(link.dataset.sessionEntryIndex);
|
|
82
89
|
}
|
|
83
90
|
ws.send(JSON.stringify(request));
|
|
84
91
|
}
|
|
@@ -91,12 +98,31 @@ export function handleClaySessionTarget(msg) {
|
|
|
91
98
|
for (var i = 0; i < links.length; i++) {
|
|
92
99
|
if ((isLog ? links[i].dataset.logRequest : links[i].dataset.sessionRequest) === msg.requestId) link = links[i];
|
|
93
100
|
}
|
|
101
|
+
if (!isLog && store.get('sessionNavigationRequestId') !== msg.requestId) {
|
|
102
|
+
if (link) {
|
|
103
|
+
link.classList.remove("is-loading");
|
|
104
|
+
delete link.dataset.sessionRequest;
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (!isLog) {
|
|
109
|
+
var requestContext = store.get('sessionNavigationRequestContext');
|
|
110
|
+
if (requestContext && (String(store.get('currentSlug')) !== String(requestContext.projectSlug) || String(store.get('activeSessionId')) !== String(requestContext.sessionId))) {
|
|
111
|
+
if (link) {
|
|
112
|
+
link.classList.remove("is-loading");
|
|
113
|
+
delete link.dataset.sessionRequest;
|
|
114
|
+
}
|
|
115
|
+
store.set({ pendingSessionEntryNavigation: null, sessionNavigationRequestContext: null });
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
94
119
|
if (link) {
|
|
95
120
|
link.classList.remove("is-loading");
|
|
96
121
|
if (isLog) delete link.dataset.logRequest;
|
|
97
122
|
else delete link.dataset.sessionRequest;
|
|
98
123
|
}
|
|
99
124
|
if (msg.status !== "ready" || !msg.target) {
|
|
125
|
+
if (!isLog) store.set({ pendingSessionEntryNavigation: null });
|
|
100
126
|
if (link) {
|
|
101
127
|
link.classList.add("is-error");
|
|
102
128
|
link.title = msg.error || "Item not available";
|
|
@@ -108,7 +134,7 @@ export function handleClaySessionTarget(msg) {
|
|
|
108
134
|
navigateToLog(msg.target);
|
|
109
135
|
return true;
|
|
110
136
|
}
|
|
111
|
-
navigateToSession({ projectSlug: msg.target.projectSlug, sessionId: msg.target.sessionId }, null);
|
|
137
|
+
navigateToSession({ projectSlug: msg.target.projectSlug, sessionId: msg.target.sessionId }, null, msg.target.entryIndex);
|
|
112
138
|
return true;
|
|
113
139
|
}
|
|
114
140
|
|
|
@@ -151,9 +177,61 @@ export function handlePaletteSessionSwitch() {
|
|
|
151
177
|
var nav = pendingNav;
|
|
152
178
|
pendingNav = null;
|
|
153
179
|
if (ctx.currentSlug && ctx.currentSlug() === nav.slug) {
|
|
154
|
-
|
|
155
|
-
|
|
180
|
+
navigateToSession({ projectSlug: nav.slug, sessionId: nav.sessionId }, nav.query, nav.entryIndex);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function handlePaletteHistoryDone() {
|
|
185
|
+
var pending = store.get('pendingSessionEntryNavigation');
|
|
186
|
+
if (!pending) return;
|
|
187
|
+
if (!isActiveEntryNavigation(pending)) {
|
|
188
|
+
store.set({ pendingSessionEntryNavigation: null });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
var historyFrom = store.get('historyFrom') || 0;
|
|
192
|
+
if (pending.entryIndex < historyFrom) {
|
|
193
|
+
var ws = getWs();
|
|
194
|
+
if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: "load_more_history", before: historyFrom, target: pending.entryIndex }));
|
|
195
|
+
return;
|
|
156
196
|
}
|
|
197
|
+
jumpToSessionEntry(pending.entryIndex);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function handlePaletteHistoryPrepended() {
|
|
201
|
+
var pending = store.get('pendingSessionEntryNavigation');
|
|
202
|
+
if (!pending) return;
|
|
203
|
+
if (!isActiveEntryNavigation(pending)) {
|
|
204
|
+
store.set({ pendingSessionEntryNavigation: null });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
requestAnimationFrame(function () {
|
|
208
|
+
if (store.get('pendingSessionEntryNavigation') === pending) jumpToSessionEntry(pending.entryIndex, pending);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function isActiveEntryNavigation(pending) {
|
|
213
|
+
return String(store.get('currentSlug') || (ctx && ctx.currentSlug ? ctx.currentSlug() : "")) === String(pending.projectSlug) && String(store.get('activeSessionId')) === String(pending.sessionId);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function jumpToSessionEntry(entryIndex, pending) {
|
|
217
|
+
if (pending && !isActiveEntryNavigation(pending)) return;
|
|
218
|
+
var messages = document.querySelectorAll("#messages [data-history-index], #messages [data-history-indexes]");
|
|
219
|
+
var target = null;
|
|
220
|
+
for (var i = 0; i < messages.length; i++) {
|
|
221
|
+
var indexes = (messages[i].dataset.historyIndexes || messages[i].dataset.historyIndex || "").split(",");
|
|
222
|
+
if (indexes.indexOf(String(entryIndex)) !== -1) { target = messages[i]; break; }
|
|
223
|
+
}
|
|
224
|
+
store.set({ pendingSessionEntryNavigation: null });
|
|
225
|
+
if (!target) return;
|
|
226
|
+
disarmStickyBottom();
|
|
227
|
+
var oldTargets = document.querySelectorAll("#messages .session-entry-target");
|
|
228
|
+
for (var oldIndex = 0; oldIndex < oldTargets.length; oldIndex++) oldTargets[oldIndex].classList.remove("session-entry-target");
|
|
229
|
+
var reducedMotion = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
230
|
+
target.scrollIntoView({ behavior: reducedMotion ? "auto" : "smooth", block: "center" });
|
|
231
|
+
target.classList.remove("session-entry-target");
|
|
232
|
+
void target.offsetWidth;
|
|
233
|
+
target.classList.add("session-entry-target");
|
|
234
|
+
setTimeout(function () { target.classList.remove("session-entry-target"); }, reducedMotion ? 700 : 1800);
|
|
157
235
|
}
|
|
158
236
|
|
|
159
237
|
function navigateToLog(target) {
|
|
@@ -357,13 +435,24 @@ function activateItem(entry) {
|
|
|
357
435
|
if (entry.type === "mate-session" || entry.type === "session") navigateToSession(entry.data, entry.query);
|
|
358
436
|
}
|
|
359
437
|
|
|
360
|
-
function navigateToSession(item, query) {
|
|
438
|
+
function navigateToSession(item, query, entryIndex) {
|
|
361
439
|
closeCommandPalette();
|
|
440
|
+
pendingNav = null;
|
|
441
|
+
var hasEntry = entryIndex !== null && entryIndex !== undefined && Number.isSafeInteger(Number(entryIndex)) && Number(entryIndex) >= 0;
|
|
442
|
+
store.set({ pendingSessionEntryNavigation: hasEntry ? { projectSlug: item.projectSlug, sessionId: item.sessionId, entryIndex: Number(entryIndex) } : null });
|
|
362
443
|
if (ctx.currentSlug && ctx.currentSlug() === item.projectSlug) {
|
|
444
|
+
var alreadyActive = String(store.get('activeSessionId')) === String(item.sessionId);
|
|
445
|
+
if (alreadyActive) {
|
|
446
|
+
if (query) setTimeout(function () { openSessionSearch(query); }, 400);
|
|
447
|
+
if (hasEntry) requestAnimationFrame(function () {
|
|
448
|
+
if (!store.get('replayingHistory')) handlePaletteHistoryDone();
|
|
449
|
+
});
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
363
452
|
ctx.selectSession(item.sessionId);
|
|
364
453
|
if (query) setTimeout(function () { openSessionSearch(query); }, 400);
|
|
365
454
|
} else {
|
|
366
|
-
pendingNav = { slug: item.projectSlug, sessionId: item.sessionId, query: query };
|
|
455
|
+
pendingNav = { slug: item.projectSlug, sessionId: item.sessionId, query: query, entryIndex: hasEntry ? Number(entryIndex) : null };
|
|
367
456
|
ctx.switchProject(item.projectSlug);
|
|
368
457
|
}
|
|
369
458
|
}
|
|
@@ -84,7 +84,7 @@ export function enhanceClaySessionLinks(root) {
|
|
|
84
84
|
fragment.appendChild(document.createTextNode(text.slice(offset, match.index)));
|
|
85
85
|
var button = document.createElement("button");
|
|
86
86
|
button.type = "button";
|
|
87
|
-
button.className = "clayos-session-link";
|
|
87
|
+
button.className = "clayos-session-link clayos-record-link";
|
|
88
88
|
button.dataset.sessionRef = match[1];
|
|
89
89
|
button.setAttribute("aria-label", "Open session" + (match[2] ? " from " + match[2].trim() : ""));
|
|
90
90
|
var label = document.createElement("span");
|
package/lib/server-home-chat.js
CHANGED
|
@@ -418,7 +418,7 @@ function attachHomeChat(deps) {
|
|
|
418
418
|
return sessionReferenceValue;
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
-
function handleMessage(ws, msg) {
|
|
421
|
+
function handleMessage(ws, msg, routedProjectSlug) {
|
|
422
422
|
if (!msg || typeof msg.type !== "string") return false;
|
|
423
423
|
if (msg.type === "home_mate_stop" || msg.type === "home_mate_permission_response") {
|
|
424
424
|
var controlUserId = ws._clayUser ? ws._clayUser.id : null;
|
|
@@ -477,8 +477,8 @@ function attachHomeChat(deps) {
|
|
|
477
477
|
if (msg.type === "issue_reference_resolve") { resolveIssueLink(ws, msg); return true; }
|
|
478
478
|
if (homeDebates.handle(ws, userId, msg)) return true;
|
|
479
479
|
if (msg.type === "home_clay_ask") { homeClayEntry.start(ws, userId, msg); return true; }
|
|
480
|
-
if (msg.type === "home_clay_session_resolve") { homeClaySessionLinks.resolve(ws, msg); return true; }
|
|
481
|
-
if (msg.type === "home_clay_log_resolve") { homeClaySessionLinks.resolveLog(ws, msg); return true; }
|
|
480
|
+
if (msg.type === "home_clay_session_resolve") { homeClaySessionLinks.resolve(ws, msg, routedProjectSlug); return true; }
|
|
481
|
+
if (msg.type === "home_clay_log_resolve") { homeClaySessionLinks.resolveLog(ws, msg, routedProjectSlug); return true; }
|
|
482
482
|
|
|
483
483
|
if (msg.type === "home_mate_debate_plan") {
|
|
484
484
|
homeDebate.start(ws, userId, msg);
|
|
@@ -2,23 +2,31 @@
|
|
|
2
2
|
// owner-bound source session.
|
|
3
3
|
|
|
4
4
|
function attachHomeClaySessionLinks(ctx) {
|
|
5
|
-
function source(ws, msg) {
|
|
5
|
+
function source(ws, msg, routedProjectSlug) {
|
|
6
6
|
var tap = msg.surface === "search" ? ws._searchClayTap : ws._homeChatTap;
|
|
7
|
+
if (!tap && routedProjectSlug) {
|
|
8
|
+
var routedProject = ctx.projects.get(routedProjectSlug);
|
|
9
|
+
var routedManager = routedProject && routedProject.getSessionManager ? routedProject.getSessionManager() : null;
|
|
10
|
+
var routedSession = routedManager && ws._clayActiveSession ? routedManager.sessions.get(ws._clayActiveSession) : null;
|
|
11
|
+
return { tap: { mateSlug: routedProjectSlug, sessionId: ws._clayActiveSession }, project: routedProject, session: routedSession };
|
|
12
|
+
}
|
|
7
13
|
var sourceProject = tap && tap.mateSlug ? ctx.projects.get(tap.mateSlug) : null;
|
|
8
14
|
var sourceManager = sourceProject && sourceProject.getSessionManager ? sourceProject.getSessionManager() : null;
|
|
9
15
|
var sourceSession = sourceManager && tap ? sourceManager.sessions.get(tap.sessionId) : null;
|
|
10
16
|
return { tap: tap, project: sourceProject, session: sourceSession };
|
|
11
17
|
}
|
|
12
18
|
|
|
13
|
-
function resolve(ws, msg) {
|
|
14
|
-
var selected = source(ws, msg);
|
|
15
|
-
var bound = ctx.workspaceQueryService && selected.session
|
|
16
|
-
? ctx.workspaceQueryService.bindProjectSession({ projectSlug: selected.tap.mateSlug, session: selected.session })
|
|
17
|
-
: null;
|
|
19
|
+
function resolve(ws, msg, routedProjectSlug) {
|
|
20
|
+
var selected = source(ws, msg, routedProjectSlug);
|
|
18
21
|
var payload = { type: "home_clay_session_target", requestId: msg.requestId || null, sessionRef: msg.sessionRef || null, status: "error" };
|
|
19
22
|
try {
|
|
23
|
+
var binding = selected.session ? { projectSlug: selected.tap.mateSlug, session: selected.session } : null;
|
|
24
|
+
if (binding && ws && ws._clayUser) binding.viewerId = ws._clayUser.id;
|
|
25
|
+
var bound = ctx.workspaceQueryService && binding
|
|
26
|
+
? ctx.workspaceQueryService.bindProjectSession(binding)
|
|
27
|
+
: null;
|
|
20
28
|
if (!bound) throw new Error("The source conversation is no longer available.");
|
|
21
|
-
payload.target = bound.resolveSessionNavigation({ sessionRef: msg.sessionRef });
|
|
29
|
+
payload.target = bound.resolveSessionNavigation({ sessionRef: msg.sessionRef, entryIndex: msg.entryIndex });
|
|
22
30
|
payload.status = "ready";
|
|
23
31
|
} catch (error) {
|
|
24
32
|
payload.error = error.message || "Session not available.";
|
|
@@ -26,8 +34,8 @@ function attachHomeClaySessionLinks(ctx) {
|
|
|
26
34
|
ctx.sendMessage(ws, payload);
|
|
27
35
|
}
|
|
28
36
|
|
|
29
|
-
function resolveLog(ws, msg) {
|
|
30
|
-
var selected = source(ws, msg);
|
|
37
|
+
function resolveLog(ws, msg, routedProjectSlug) {
|
|
38
|
+
var selected = source(ws, msg, routedProjectSlug);
|
|
31
39
|
var status = selected.project && selected.project.getStatus ? selected.project.getStatus() : null;
|
|
32
40
|
var bound = ctx.projectLogsService && selected.session && status
|
|
33
41
|
? ctx.projectLogsService.bindMate({
|
package/lib/server.js
CHANGED
|
@@ -1630,7 +1630,7 @@ function createServer(opts) {
|
|
|
1630
1630
|
if (dmHandler.handleMessage(ws, msg)) return true;
|
|
1631
1631
|
if (mateHandler && mateHandler.handleMessage(ws, msg)) return true;
|
|
1632
1632
|
if (emailHandler.handleMessage(ws, msg)) return true;
|
|
1633
|
-
if (homeChatHandler.handleMessage(ws, msg)) return true;
|
|
1633
|
+
if (homeChatHandler.handleMessage(ws, msg, routedProjectSlug)) return true;
|
|
1634
1634
|
if (homePreferencesHandler.handleMessage(ws, msg)) return true;
|
|
1635
1635
|
if (toolsHandler.handleMessage(ws, msg)) return true;
|
|
1636
1636
|
return false;
|
package/lib/sessions.js
CHANGED
|
@@ -864,7 +864,9 @@ function createSessionManager(opts) {
|
|
|
864
864
|
if (_item && (_item.type === "mention_user" || _item.type === "mention_response")) {
|
|
865
865
|
console.log("[DEBUG replayHistory] sending mention at index=" + i + " from=" + fromIndex + " total=" + total + " type=" + _item.type + " mate=" + (_item.mateName || ""));
|
|
866
866
|
}
|
|
867
|
-
|
|
867
|
+
var replayItem = transform ? transform(_item) : _item;
|
|
868
|
+
if (replayItem && typeof replayItem === "object") replayItem = Object.assign({}, replayItem, { _historyIndex: i });
|
|
869
|
+
_send(replayItem);
|
|
868
870
|
}
|
|
869
871
|
|
|
870
872
|
// Find the last result message in the full history for accurate context data
|
|
@@ -168,6 +168,13 @@ function attachWorkspaceQueryService(ctx) {
|
|
|
168
168
|
} else if (source.session.ownerId) {
|
|
169
169
|
return null;
|
|
170
170
|
}
|
|
171
|
+
if (source.viewerId !== undefined) {
|
|
172
|
+
var viewerPrincipal = {
|
|
173
|
+
userId: isMultiUser() ? source.viewerId : null,
|
|
174
|
+
singleUserOwnerId: isMultiUser() ? null : source.viewerId,
|
|
175
|
+
};
|
|
176
|
+
if (!ownsSession(viewerPrincipal, source.session)) return null;
|
|
177
|
+
}
|
|
171
178
|
return createBound({
|
|
172
179
|
userId: isMultiUser() ? source.session.ownerId : null,
|
|
173
180
|
sourceProjectSlug: source.projectSlug,
|
|
@@ -366,11 +373,13 @@ function attachWorkspaceQueryService(ctx) {
|
|
|
366
373
|
|
|
367
374
|
function resolveSessionNavigation(principal, ref) {
|
|
368
375
|
if (typeof ref !== "string" || !/^session:[A-Za-z0-9_-]{24}$/.test(ref)) throw new Error("Invalid session reference.");
|
|
376
|
+
var entryIndex = arguments.length > 2 ? arguments[2] : null;
|
|
377
|
+
if (entryIndex !== null && entryIndex !== undefined && (!Number.isSafeInteger(entryIndex) || entryIndex < 0)) throw new Error("Invalid session entry reference.");
|
|
369
378
|
var projects = collect(principal);
|
|
370
379
|
for (var p = 0; p < projects.length; p++) {
|
|
371
380
|
var session = findSession(projects[p], ref);
|
|
372
381
|
if (!session) continue;
|
|
373
|
-
|
|
382
|
+
var target = {
|
|
374
383
|
projectSlug: projects[p].projectSlug,
|
|
375
384
|
projectTitle: projects[p].projectTitle,
|
|
376
385
|
isMate: projects[p].isMate,
|
|
@@ -379,6 +388,8 @@ function attachWorkspaceQueryService(ctx) {
|
|
|
379
388
|
homeSessionId: session.cliSessionId || "local:" + session.localId,
|
|
380
389
|
title: cleanMetadata(session.title || "New Session", 160),
|
|
381
390
|
};
|
|
391
|
+
if (entryIndex !== null && entryIndex !== undefined) target.entryIndex = entryIndex;
|
|
392
|
+
return target;
|
|
382
393
|
}
|
|
383
394
|
throw new Error("Session not found in the owned workspace.");
|
|
384
395
|
}
|
|
@@ -450,7 +461,7 @@ function attachWorkspaceQueryService(ctx) {
|
|
|
450
461
|
if (!principal.isClay) throw new Error("Workspace-wide activity is available only to builtin Clay.");
|
|
451
462
|
return workspaceActivity(principal, args || {});
|
|
452
463
|
},
|
|
453
|
-
resolveSessionNavigation: function (args) { return resolveSessionNavigation(principal, args && args.sessionRef); },
|
|
464
|
+
resolveSessionNavigation: function (args) { return resolveSessionNavigation(principal, args && args.sessionRef, args && args.entryIndex); },
|
|
454
465
|
listRecentDecisions: function (args) {
|
|
455
466
|
if (!principal.isClay) throw new Error("Workspace decisions are available only to builtin Clay.");
|
|
456
467
|
return recentDecisions(principal, args || {});
|
package/package.json
CHANGED