clay-server 4.0.0-beta.16 → 4.0.0-beta.18
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/daemon-projects.js +10 -5
- package/lib/daemon.js +61 -10
- package/lib/project-log-context.js +171 -0
- package/lib/project-log-feedback-delivery.js +47 -5
- package/lib/project-logs-context-state.js +42 -0
- package/lib/project-logs-mcp-server.js +15 -4
- package/lib/project-logs-query.js +18 -0
- package/lib/project-logs-root.js +8 -0
- package/lib/project-logs-service.js +121 -15
- package/lib/project-logs-snapshot.js +3 -0
- package/lib/project-logs-store.js +29 -32
- package/lib/project-logs-versioning.js +6 -2
- package/lib/project-logs.js +61 -13
- package/lib/project-message-delivery.js +70 -0
- package/lib/project-user-message.js +11 -0
- package/lib/project.js +6 -1
- package/lib/public/app.js +5 -0
- package/lib/public/css/command-palette.css +19 -9
- package/lib/public/css/project-logs.css +20 -2
- package/lib/public/css/sticky-notes.css +7 -0
- package/lib/public/index.html +2 -2
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +22 -3
- package/lib/public/modules/app-messages.js +29 -1
- package/lib/public/modules/app-rendering.js +1 -0
- package/lib/public/modules/chat-bubble-renderer.js +1 -1
- package/lib/public/modules/clay-log-links.js +70 -0
- package/lib/public/modules/command-palette.js +52 -15
- package/lib/public/modules/input.js +6 -1
- package/lib/public/modules/markdown.js +2 -0
- package/lib/public/modules/message-delivery.js +97 -0
- package/lib/public/modules/project-logs-render.js +48 -2
- package/lib/public/modules/project-logs.js +140 -11
- package/lib/public/modules/sidebar-session-hierarchy.js +16 -7
- package/lib/public/modules/sidebar-sessions.js +3 -1
- package/lib/public/modules/sticky-note-markdown.js +5 -0
- package/lib/public/modules/sticky-notes-browser.js +2 -0
- package/lib/public/modules/sticky-notes-card.js +2 -0
- package/lib/public/modules/sticky-notes-editor.js +3 -0
- package/lib/public/modules/sticky-notes.js +2 -0
- package/lib/public/style.css +1 -1
- package/lib/sdk-bridge.js +20 -10
- package/lib/server-global-ws.js +4 -0
- package/lib/server-home-chat-events.js +3 -0
- package/lib/server-home-chat.js +9 -2
- package/lib/server-home-clay-entry.js +1 -1
- package/lib/server-home-clay-session-links.js +34 -5
- package/lib/server.js +14 -3
- package/lib/ws-schema.js +8 -2
- package/package.json +1 -1
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
// record, and Back returns to the list with the query, filter, and scroll
|
|
7
7
|
// position intact.
|
|
8
8
|
//
|
|
9
|
-
// People read and comment
|
|
10
|
-
// this project's agent sessions through the
|
|
11
|
-
// module has no create or edit affordance
|
|
9
|
+
// People read and comment, and project owners may delete. Canonical entries
|
|
10
|
+
// are created and revised only by this project's agent sessions through the
|
|
11
|
+
// clay-logs MCP tools, so this module has no create or edit affordance.
|
|
12
12
|
|
|
13
13
|
import { store } from './store.js';
|
|
14
14
|
import { getWs } from './ws-ref.js';
|
|
@@ -18,14 +18,17 @@ import { closeScheduler } from './scheduler.js';
|
|
|
18
18
|
import { closeNotesBrowser } from './sticky-notes-browser.js';
|
|
19
19
|
import { closeFileViewer } from './filebrowser.js';
|
|
20
20
|
import { closeTerminal } from './terminal.js';
|
|
21
|
-
import { renderFilter, renderList, renderDetail } from './project-logs-render.js';
|
|
21
|
+
import { renderFilter, renderContextFilter, renderList, renderDetail } from './project-logs-render.js';
|
|
22
22
|
|
|
23
23
|
var panel = null;
|
|
24
24
|
var listEl = null;
|
|
25
25
|
var detailEl = null;
|
|
26
26
|
var searchEl = null;
|
|
27
27
|
var filterEl = null;
|
|
28
|
+
var contextFilterEl = null;
|
|
28
29
|
var backBtn = null;
|
|
30
|
+
var deleteDialog = null;
|
|
31
|
+
var pendingDeleteEntry = null;
|
|
29
32
|
var searchTimer = null;
|
|
30
33
|
var requestCounter = 0;
|
|
31
34
|
|
|
@@ -93,17 +96,32 @@ function ensurePanel() {
|
|
|
93
96
|
'</header>' +
|
|
94
97
|
'<div id="project-logs-toolbar" class="project-logs-toolbar">' +
|
|
95
98
|
'<label class="project-logs-search"><i data-lucide="search"></i><input id="project-logs-search" type="search" placeholder="Search logs" autocomplete="off"></label>' +
|
|
99
|
+
'<div id="project-logs-context-filter-slot"></div>' +
|
|
96
100
|
'<div id="project-logs-filter-slot"></div>' +
|
|
97
101
|
'</div>' +
|
|
98
102
|
'<div id="project-logs-list" class="project-logs-list" role="list"></div>' +
|
|
99
|
-
'<main id="project-logs-detail" class="project-logs-detail hidden"></main>'
|
|
103
|
+
'<main id="project-logs-detail" class="project-logs-detail hidden"></main>' +
|
|
104
|
+
'<div id="project-log-delete-dialog" class="project-log-delete-dialog hidden" role="dialog" aria-modal="true" aria-labelledby="project-log-delete-heading">' +
|
|
105
|
+
'<div class="project-log-delete-dialog-card">' +
|
|
106
|
+
'<h2 id="project-log-delete-heading">Delete this log?</h2>' +
|
|
107
|
+
'<p>This removes the entry from the project ledger. It cannot be restored in the app.</p>' +
|
|
108
|
+
'<p class="project-log-delete-dialog-title"></p>' +
|
|
109
|
+
'<p class="project-log-delete-dialog-status" role="status"></p>' +
|
|
110
|
+
'<div class="project-log-delete-dialog-actions">' +
|
|
111
|
+
'<button type="button" data-action="cancel">Cancel</button>' +
|
|
112
|
+
'<button type="button" class="danger" data-action="delete">Delete log</button>' +
|
|
113
|
+
'</div>' +
|
|
114
|
+
'</div>' +
|
|
115
|
+
'</div>';
|
|
100
116
|
panels.appendChild(panel);
|
|
101
117
|
|
|
102
118
|
listEl = panel.querySelector("#project-logs-list");
|
|
103
119
|
detailEl = panel.querySelector("#project-logs-detail");
|
|
104
120
|
searchEl = panel.querySelector("#project-logs-search");
|
|
105
121
|
filterEl = panel.querySelector("#project-logs-filter-slot");
|
|
122
|
+
contextFilterEl = panel.querySelector("#project-logs-context-filter-slot");
|
|
106
123
|
backBtn = panel.querySelector("#project-logs-back");
|
|
124
|
+
deleteDialog = panel.querySelector("#project-log-delete-dialog");
|
|
107
125
|
|
|
108
126
|
panel.querySelector("#project-logs-close").addEventListener("click", closeProjectLogs);
|
|
109
127
|
backBtn.addEventListener("click", showList);
|
|
@@ -117,6 +135,14 @@ function ensurePanel() {
|
|
|
117
135
|
if (searchTimer) clearTimeout(searchTimer);
|
|
118
136
|
searchTimer = setTimeout(requestList, 180);
|
|
119
137
|
});
|
|
138
|
+
deleteDialog.querySelector('[data-action="cancel"]').addEventListener("click", closeDeleteDialog);
|
|
139
|
+
deleteDialog.querySelector('[data-action="delete"]').addEventListener("click", confirmDelete);
|
|
140
|
+
deleteDialog.addEventListener("click", function (event) {
|
|
141
|
+
if (event.target === deleteDialog) closeDeleteDialog();
|
|
142
|
+
});
|
|
143
|
+
deleteDialog.addEventListener("keydown", function (event) {
|
|
144
|
+
if (event.key === "Escape") closeDeleteDialog();
|
|
145
|
+
});
|
|
120
146
|
refreshIcons();
|
|
121
147
|
}
|
|
122
148
|
|
|
@@ -159,7 +185,48 @@ function showDetail(entry) {
|
|
|
159
185
|
detailEl.classList.remove("hidden");
|
|
160
186
|
detailEl.scrollTop = 0;
|
|
161
187
|
backBtn.classList.remove("hidden");
|
|
162
|
-
renderDetail(detailEl, entry, {
|
|
188
|
+
renderDetail(detailEl, entry, {
|
|
189
|
+
onComment: submitComment,
|
|
190
|
+
onDelete: openDeleteDialog,
|
|
191
|
+
canDelete: store.get('projectLogsCanDelete') === true,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function openDeleteDialog(entry) {
|
|
196
|
+
if (!deleteDialog || !entry || store.get('projectLogsCanDelete') !== true) return;
|
|
197
|
+
pendingDeleteEntry = entry;
|
|
198
|
+
deleteDialog.querySelector(".project-log-delete-dialog-title").textContent = entry.title || "Untitled log";
|
|
199
|
+
deleteDialog.querySelector(".project-log-delete-dialog-status").textContent = "";
|
|
200
|
+
deleteDialog.querySelector('[data-action="delete"]').disabled = false;
|
|
201
|
+
deleteDialog.querySelector('[data-action="cancel"]').disabled = false;
|
|
202
|
+
deleteDialog.classList.remove("hidden");
|
|
203
|
+
deleteDialog.querySelector('[data-action="cancel"]').focus();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function closeDeleteDialog(force) {
|
|
207
|
+
if (!deleteDialog) return;
|
|
208
|
+
if (!force && store.get('projectLogsDeleteRequestId')) return;
|
|
209
|
+
deleteDialog.classList.add("hidden");
|
|
210
|
+
pendingDeleteEntry = null;
|
|
211
|
+
store.set({ projectLogsDeleteRequestId: null });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function confirmDelete() {
|
|
215
|
+
if (!pendingDeleteEntry || !deleteDialog) return;
|
|
216
|
+
var requestId = nextRequestId("delete-log");
|
|
217
|
+
var status = deleteDialog.querySelector(".project-log-delete-dialog-status");
|
|
218
|
+
var button = deleteDialog.querySelector('[data-action="delete"]');
|
|
219
|
+
var cancelButton = deleteDialog.querySelector('[data-action="cancel"]');
|
|
220
|
+
store.set({ projectLogsDeleteRequestId: requestId });
|
|
221
|
+
button.disabled = true;
|
|
222
|
+
cancelButton.disabled = true;
|
|
223
|
+
status.textContent = "Deleting...";
|
|
224
|
+
if (!send({ type: "project_log_delete", requestId: requestId, ref: pendingDeleteEntry.ref })) {
|
|
225
|
+
button.disabled = false;
|
|
226
|
+
cancelButton.disabled = false;
|
|
227
|
+
store.set({ projectLogsDeleteRequestId: null });
|
|
228
|
+
status.textContent = "Logs are unavailable while disconnected.";
|
|
229
|
+
}
|
|
163
230
|
}
|
|
164
231
|
|
|
165
232
|
// --- Requests ------------------------------------------------------------
|
|
@@ -176,6 +243,7 @@ function requestList() {
|
|
|
176
243
|
requestId: requestId,
|
|
177
244
|
query: searchEl ? searchEl.value.trim() : "",
|
|
178
245
|
category: store.get('projectLogsCategory') || "",
|
|
246
|
+
contextMode: store.get('projectLogsContextMode') || "",
|
|
179
247
|
})) {
|
|
180
248
|
if (listEl) {
|
|
181
249
|
listEl.removeAttribute("aria-busy");
|
|
@@ -206,6 +274,11 @@ function applyFilter(category) {
|
|
|
206
274
|
requestList();
|
|
207
275
|
}
|
|
208
276
|
|
|
277
|
+
function applyContextFilter(contextMode) {
|
|
278
|
+
store.set({ projectLogsContextMode: contextMode || "project" });
|
|
279
|
+
requestList();
|
|
280
|
+
}
|
|
281
|
+
|
|
209
282
|
// --- Lifecycle -----------------------------------------------------------
|
|
210
283
|
|
|
211
284
|
export function initProjectLogs() {
|
|
@@ -227,10 +300,14 @@ export function initProjectLogs() {
|
|
|
227
300
|
projectLogsSelectedRef: null,
|
|
228
301
|
projectLogsView: "list",
|
|
229
302
|
projectLogsCategory: "",
|
|
303
|
+
projectLogsContextMode: "",
|
|
304
|
+
projectLogsIsWorktree: false,
|
|
230
305
|
projectLogsListScroll: 0,
|
|
231
306
|
projectLogsListRequestId: null,
|
|
232
307
|
projectLogsReadRequestId: null,
|
|
233
308
|
projectLogsCommentRequestId: null,
|
|
309
|
+
projectLogsDeleteRequestId: null,
|
|
310
|
+
projectLogsCanDelete: false,
|
|
234
311
|
projectLogsUnread: 0,
|
|
235
312
|
projectLogsSeenRevisions: {}
|
|
236
313
|
});
|
|
@@ -259,11 +336,19 @@ export function openProjectLogs() {
|
|
|
259
336
|
requestList();
|
|
260
337
|
}
|
|
261
338
|
|
|
339
|
+
export function openProjectLog(ref) {
|
|
340
|
+
if (typeof ref !== "string" || !/^log:[A-Za-z0-9_-]{24}$/.test(ref)) return false;
|
|
341
|
+
openProjectLogs();
|
|
342
|
+
requestEntry(ref);
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
|
|
262
346
|
export function closeProjectLogs() {
|
|
263
347
|
if (!store.get('projectLogsOpen')) return;
|
|
264
348
|
if (panel) {
|
|
265
349
|
panel.classList.add("hidden");
|
|
266
350
|
}
|
|
351
|
+
closeDeleteDialog(true);
|
|
267
352
|
// Fullscreen is always dropped on close so the next open is a bounded pane
|
|
268
353
|
// and never silently hides the conversation.
|
|
269
354
|
applyWindowState(store.get('projectLogsWide'), false);
|
|
@@ -276,21 +361,31 @@ export function closeProjectLogs() {
|
|
|
276
361
|
|
|
277
362
|
export function handleProjectLogsState(msg) {
|
|
278
363
|
if (msg.requestId && msg.requestId !== store.get('projectLogsListRequestId')) return;
|
|
279
|
-
store.
|
|
364
|
+
var contextMode = msg.contextMode || store.get('projectLogsContextMode') || (msg.isWorktree ? "current" : "project");
|
|
365
|
+
store.set({
|
|
366
|
+
projectLogsEntries: msg.entries || [],
|
|
367
|
+
projectLogsCanDelete: msg.canDelete === true,
|
|
368
|
+
projectLogsContextMode: contextMode,
|
|
369
|
+
projectLogsIsWorktree: msg.isWorktree === true,
|
|
370
|
+
});
|
|
280
371
|
if (!listEl) return;
|
|
281
372
|
listEl.removeAttribute("aria-busy");
|
|
282
373
|
if (filterEl && Array.isArray(msg.categories)) {
|
|
283
374
|
renderFilter(filterEl, msg.categories, store.get('projectLogsCategory'), applyFilter);
|
|
284
375
|
}
|
|
285
|
-
|
|
376
|
+
if (contextFilterEl) {
|
|
377
|
+
renderContextFilter(contextFilterEl, contextMode, msg.isWorktree === true, applyContextFilter);
|
|
378
|
+
}
|
|
379
|
+
var isFiltered = !!((searchEl && searchEl.value.trim()) || store.get('projectLogsCategory') || contextMode !== "project");
|
|
286
380
|
renderList(listEl, msg.entries || [], requestEntry,
|
|
287
|
-
isFiltered ? "No entries match
|
|
381
|
+
isFiltered ? "No entries match the current scope, search, or category." : "No logs yet. This project's agent sessions record decisions and work here.");
|
|
288
382
|
if (store.get('projectLogsView') !== "detail") listEl.scrollTop = store.get('projectLogsListScroll') || 0;
|
|
289
383
|
}
|
|
290
384
|
|
|
291
385
|
export function handleProjectLogEntry(msg) {
|
|
292
386
|
if (msg.requestId && msg.requestId !== store.get('projectLogsReadRequestId')) return;
|
|
293
387
|
if (!msg.entry) return;
|
|
388
|
+
store.set({ projectLogsCanDelete: msg.canDelete === true });
|
|
294
389
|
showDetail(msg.entry);
|
|
295
390
|
}
|
|
296
391
|
|
|
@@ -300,18 +395,45 @@ export function handleProjectLogCommented(msg) {
|
|
|
300
395
|
// The comment is a proposal, not a change, so say so plainly rather than
|
|
301
396
|
// leaving the composer reading "Posting..." forever.
|
|
302
397
|
var pendingStatus = store.get('projectLogsCommentStatusEl');
|
|
303
|
-
if (pendingStatus) pendingStatus.textContent =
|
|
398
|
+
if (pendingStatus) pendingStatus.textContent = msg.reviewQueued
|
|
399
|
+
? "Project Driver is reviewing..."
|
|
400
|
+
: "Awaiting Project Driver review";
|
|
304
401
|
store.set({ projectLogsCommentStatusEl: null });
|
|
305
402
|
// Re-render the open entry so the new comment and its attribution appear,
|
|
306
403
|
// then refresh the ledger so the comment count is current.
|
|
307
404
|
showDetail(msg.entry);
|
|
405
|
+
if (msg.reviewQueued && detailEl) {
|
|
406
|
+
var badges = detailEl.querySelectorAll(".project-log-comment-status-badge");
|
|
407
|
+
if (badges.length > 0) badges[badges.length - 1].textContent = "Project Driver is reviewing";
|
|
408
|
+
}
|
|
409
|
+
requestList();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function handleProjectLogCommentReviewed(msg) {
|
|
413
|
+
if (!msg || !msg.ref || !store.get('projectLogsOpen')) return;
|
|
414
|
+
if (store.get('projectLogsView') === "detail" && msg.ref === store.get('projectLogsSelectedRef')) {
|
|
415
|
+
requestEntry(msg.ref);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
requestList();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export function handleProjectLogDeleted(msg) {
|
|
422
|
+
if (msg.requestId && msg.requestId !== store.get('projectLogsDeleteRequestId')) return;
|
|
423
|
+
closeDeleteDialog(true);
|
|
424
|
+
showList();
|
|
308
425
|
requestList();
|
|
426
|
+
showToast("Log deleted.");
|
|
309
427
|
}
|
|
310
428
|
|
|
311
429
|
// A canonical revision landed. This marks the explicit button only; it never
|
|
312
430
|
// opens, reveals, focuses, or scrolls the pane.
|
|
313
431
|
export function handleProjectLogUpdated(msg) {
|
|
314
432
|
noteCanonicalUpdate(msg);
|
|
433
|
+
if (msg.op === "delete" && msg.ref === store.get('projectLogsSelectedRef')) {
|
|
434
|
+
closeDeleteDialog(true);
|
|
435
|
+
showList();
|
|
436
|
+
}
|
|
315
437
|
// Refresh the ledger only when it is already on screen, so the row moves to
|
|
316
438
|
// the top the moment its canonical update arrives.
|
|
317
439
|
if (store.get('projectLogsOpen') && store.get('projectLogsView') !== "detail") requestList();
|
|
@@ -321,12 +443,19 @@ export function handleProjectLogsError(msg) {
|
|
|
321
443
|
var pending = [
|
|
322
444
|
store.get('projectLogsListRequestId'),
|
|
323
445
|
store.get('projectLogsReadRequestId'),
|
|
324
|
-
store.get('projectLogsCommentRequestId')
|
|
446
|
+
store.get('projectLogsCommentRequestId'),
|
|
447
|
+
store.get('projectLogsDeleteRequestId')
|
|
325
448
|
];
|
|
326
449
|
if (msg.requestId && pending.indexOf(msg.requestId) === -1) return;
|
|
327
450
|
if (msg.requestId && msg.requestId === store.get('projectLogsListRequestId') && listEl) {
|
|
328
451
|
listEl.removeAttribute("aria-busy");
|
|
329
452
|
renderList(listEl, [], requestEntry, "The ledger could not be loaded. Try opening Logs again.");
|
|
330
453
|
}
|
|
454
|
+
if (msg.requestId && msg.requestId === store.get('projectLogsDeleteRequestId') && deleteDialog) {
|
|
455
|
+
store.set({ projectLogsDeleteRequestId: null });
|
|
456
|
+
deleteDialog.querySelector('[data-action="delete"]').disabled = false;
|
|
457
|
+
deleteDialog.querySelector('[data-action="cancel"]').disabled = false;
|
|
458
|
+
deleteDialog.querySelector(".project-log-delete-dialog-status").textContent = msg.message || "The log could not be deleted.";
|
|
459
|
+
}
|
|
331
460
|
showToast(msg.message || "Project Logs could not complete the request.", "error");
|
|
332
461
|
}
|
|
@@ -55,6 +55,10 @@ export function hierarchyItemMatches(item, matchIds) {
|
|
|
55
55
|
return true;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export function isDesktopHierarchyToggleEvent(event) {
|
|
59
|
+
return !!(event && event.target && typeof event.target.closest === "function" && event.target.closest(".session-driver-toggle"));
|
|
60
|
+
}
|
|
61
|
+
|
|
58
62
|
function expanded(surface, key, workers, matchIds) {
|
|
59
63
|
var state = expansionBySurface[surface];
|
|
60
64
|
if (state.has(key)) return state.get(key);
|
|
@@ -93,20 +97,25 @@ export function renderDesktopDriverHierarchy(root, renderSession, rerender, matc
|
|
|
93
97
|
control.setAttribute("aria-controls", childrenId);
|
|
94
98
|
control.setAttribute("aria-label", (isExpanded ? "Collapse" : "Expand") + " Workers for " + (root.driver.title || "New Session"));
|
|
95
99
|
control.innerHTML = iconHtml("chevron-right");
|
|
100
|
+
var children = document.createElement("div");
|
|
101
|
+
children.id = childrenId;
|
|
102
|
+
children.className = "session-worker-children";
|
|
103
|
+
children.setAttribute("role", "group");
|
|
104
|
+
children.setAttribute("aria-label", "Workers for " + (root.driver.title || "New Session"));
|
|
105
|
+
children.hidden = !isExpanded;
|
|
96
106
|
control.addEventListener("click", function (event) {
|
|
97
107
|
event.preventDefault();
|
|
98
108
|
event.stopPropagation();
|
|
99
|
-
|
|
109
|
+
var nextExpanded = !isExpanded;
|
|
110
|
+
expansionBySurface.desktop.set(key, nextExpanded);
|
|
111
|
+
control.setAttribute("aria-expanded", String(nextExpanded));
|
|
112
|
+
control.setAttribute("aria-label", (nextExpanded ? "Collapse" : "Expand") + " Workers for " + (root.driver.title || "New Session"));
|
|
113
|
+
children.hidden = !nextExpanded;
|
|
114
|
+
isExpanded = nextExpanded;
|
|
100
115
|
});
|
|
101
116
|
row.insertBefore(control, row.firstChild);
|
|
102
117
|
wrapper.appendChild(row);
|
|
103
118
|
|
|
104
|
-
var children = document.createElement("div");
|
|
105
|
-
children.id = childrenId;
|
|
106
|
-
children.className = "session-worker-children";
|
|
107
|
-
children.setAttribute("role", "group");
|
|
108
|
-
children.setAttribute("aria-label", "Workers for " + (root.driver.title || "New Session"));
|
|
109
|
-
children.hidden = !isExpanded;
|
|
110
119
|
var current = currentWorkerIds();
|
|
111
120
|
for (var i = 0; i < root.workers.length; i++) {
|
|
112
121
|
if (!visibleWorker(root.workers[i], root.driver, matchIds)) continue;
|
|
@@ -18,6 +18,7 @@ import { groupedSessionIds } from './split-group-helpers.js';
|
|
|
18
18
|
import { openPairDialog } from './split-pair-ui.js';
|
|
19
19
|
import {
|
|
20
20
|
hierarchyItemMatches,
|
|
21
|
+
isDesktopHierarchyToggleEvent,
|
|
21
22
|
prepareSidebarHierarchy,
|
|
22
23
|
renderDesktopDriverHierarchy,
|
|
23
24
|
renderDesktopOrphanHierarchy
|
|
@@ -1333,7 +1334,8 @@ function renderSessionItem(s, options) {
|
|
|
1333
1334
|
appendSessionCloseButton(el, s);
|
|
1334
1335
|
|
|
1335
1336
|
el.addEventListener("click", (function (id) {
|
|
1336
|
-
return function () {
|
|
1337
|
+
return function (event) {
|
|
1338
|
+
if (isDesktopHierarchyToggleEvent(event)) return;
|
|
1337
1339
|
if (getWs() && store.get('connected')) {
|
|
1338
1340
|
var pendingQuery = searchQuery || "";
|
|
1339
1341
|
getWs().send(JSON.stringify({ type: "switch_session", id: id }));
|
|
@@ -58,6 +58,11 @@ function nodeToMarkdown(node) {
|
|
|
58
58
|
return "\n" + inner;
|
|
59
59
|
case "P": return "\n" + inner;
|
|
60
60
|
case "A": return node.getAttribute("href") || inner;
|
|
61
|
+
case "BUTTON":
|
|
62
|
+
if (node.classList.contains("clayos-log-link") && node.dataset && node.dataset.logRef) {
|
|
63
|
+
return "`" + node.dataset.logRef + "`";
|
|
64
|
+
}
|
|
65
|
+
return inner;
|
|
61
66
|
case "SPAN":
|
|
62
67
|
if (node.classList.contains("sn-check")) {
|
|
63
68
|
return node.classList.contains("checked") ? "- [x]" : "- [ ]";
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { store } from './store.js';
|
|
12
12
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
13
13
|
import { renderMiniMarkdown, getTitle } from './sticky-note-markdown.js';
|
|
14
|
+
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
14
15
|
import { send } from './sticky-notes-shared.js';
|
|
15
16
|
import { listNoteData, focusNoteOnCanvas, hideNotes, showNotes } from './sticky-notes.js';
|
|
16
17
|
|
|
@@ -205,6 +206,7 @@ function buildCard(data) {
|
|
|
205
206
|
var bodyLines = (data.text || "").split("\n").slice(1).join("\n").trim();
|
|
206
207
|
if (bodyLines) {
|
|
207
208
|
body.innerHTML = renderMiniMarkdown("_\n" + bodyLines).replace('<div class="sn-title">_</div>', "");
|
|
209
|
+
enhanceClayLogLinks(body);
|
|
208
210
|
}
|
|
209
211
|
card.appendChild(body);
|
|
210
212
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
9
9
|
import { VENDOR_AVATARS } from './app-rendering.js';
|
|
10
10
|
import { getTitle, renderMiniMarkdown } from './sticky-note-markdown.js';
|
|
11
|
+
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
11
12
|
import {
|
|
12
13
|
NOTE_COLORS,
|
|
13
14
|
isClosedNote,
|
|
@@ -139,6 +140,7 @@ export function renderNote(data) {
|
|
|
139
140
|
rendered.spellcheck = true;
|
|
140
141
|
if (data.text && data.text.trim()) {
|
|
141
142
|
rendered.innerHTML = renderMiniMarkdown(data.text);
|
|
143
|
+
enhanceClayLogLinks(rendered);
|
|
142
144
|
} else {
|
|
143
145
|
rendered.classList.add("is-empty");
|
|
144
146
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
9
9
|
import { extractMarkdown, renderMiniMarkdown } from './sticky-note-markdown.js';
|
|
10
|
+
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
10
11
|
import { debouncedTextUpdate, syncTitle } from './sticky-notes-shared.js';
|
|
11
12
|
|
|
12
13
|
var formatToolbarEl = null;
|
|
@@ -183,6 +184,7 @@ export function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
|
183
184
|
syncTitle(noteEl, md);
|
|
184
185
|
if (md.trim()) {
|
|
185
186
|
rendered.innerHTML = renderMiniMarkdown(md);
|
|
187
|
+
enhanceClayLogLinks(rendered);
|
|
186
188
|
rendered.classList.remove("is-empty");
|
|
187
189
|
} else {
|
|
188
190
|
rendered.innerHTML = "";
|
|
@@ -217,6 +219,7 @@ export function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
|
217
219
|
textarea.value = md;
|
|
218
220
|
if (md.trim()) {
|
|
219
221
|
rendered.innerHTML = renderMiniMarkdown(md);
|
|
222
|
+
enhanceClayLogLinks(rendered);
|
|
220
223
|
rendered.classList.remove("is-empty");
|
|
221
224
|
} else {
|
|
222
225
|
rendered.innerHTML = "";
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
9
9
|
import { renderMiniMarkdown } from './sticky-note-markdown.js';
|
|
10
|
+
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
10
11
|
import { isClosedNote, send, clampPos, clearNoteTimers, syncTitle } from './sticky-notes-shared.js';
|
|
11
12
|
import { renderNote, closeColorPicker } from './sticky-notes-card.js';
|
|
12
13
|
import { closeFormatToolbar, isFormatToolbarOpen } from './sticky-notes-editor.js';
|
|
@@ -240,6 +241,7 @@ function patchNote(entry, next) {
|
|
|
240
241
|
if (textarea) textarea.value = nextText;
|
|
241
242
|
if (nextText.trim()) {
|
|
242
243
|
rendered.innerHTML = renderMiniMarkdown(nextText);
|
|
244
|
+
enhanceClayLogLinks(rendered);
|
|
243
245
|
rendered.classList.remove("is-empty");
|
|
244
246
|
} else {
|
|
245
247
|
rendered.innerHTML = "";
|
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=20260908-log-links2");
|
|
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
|
@@ -1623,6 +1623,21 @@ function createSDKBridge(opts) {
|
|
|
1623
1623
|
if (selectedUserInputMode === "fallback") {
|
|
1624
1624
|
sessionToolDefs = sessionToolDefs.concat(yoke.userInput.fallbackToolDefs(sessionUserInputHandler));
|
|
1625
1625
|
}
|
|
1626
|
+
function findSessionToolDef(toolName) {
|
|
1627
|
+
// Codex restores a thread's original dynamic-tool catalog on resume, but
|
|
1628
|
+
// pair state can change after this query starts. Resolve live session
|
|
1629
|
+
// handlers first so the dispatcher cannot lag behind that catalog. Keep
|
|
1630
|
+
// the query snapshot as a fallback for query-bound tools such as the
|
|
1631
|
+
// structured-input fallback, whose handler exists only for this turn.
|
|
1632
|
+
var liveToolDefs = getSessionToolDefs(session) || [];
|
|
1633
|
+
for (var liveIndex = 0; liveIndex < liveToolDefs.length; liveIndex++) {
|
|
1634
|
+
if (liveToolDefs[liveIndex].name === toolName) return liveToolDefs[liveIndex];
|
|
1635
|
+
}
|
|
1636
|
+
for (var snapshotIndex = 0; snapshotIndex < sessionToolDefs.length; snapshotIndex++) {
|
|
1637
|
+
if (sessionToolDefs[snapshotIndex].name === toolName) return sessionToolDefs[snapshotIndex];
|
|
1638
|
+
}
|
|
1639
|
+
return null;
|
|
1640
|
+
}
|
|
1626
1641
|
|
|
1627
1642
|
// Give fresh queries an immediate provisional label. The visible user
|
|
1628
1643
|
// message in history wins over `text`, which may contain an internal
|
|
@@ -1663,10 +1678,9 @@ function createSDKBridge(opts) {
|
|
|
1663
1678
|
toolServerDescriptors: extractMcpDescriptors(mergedMcpServers) || undefined,
|
|
1664
1679
|
dynamicTools: sessionToolDefs,
|
|
1665
1680
|
callDynamicTool: function (toolName, args) {
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
}
|
|
1681
|
+
var toolDef = findSessionToolDef(toolName);
|
|
1682
|
+
if (toolDef && typeof toolDef.handler === "function") {
|
|
1683
|
+
return toolDef.handler(args || {});
|
|
1670
1684
|
}
|
|
1671
1685
|
return Promise.reject(new Error("Session tool not found: " + toolName));
|
|
1672
1686
|
},
|
|
@@ -1674,12 +1688,8 @@ function createSDKBridge(opts) {
|
|
|
1674
1688
|
abortController: linuxUser ? undefined : session.abortController,
|
|
1675
1689
|
canUseTool: function(toolName, input, toolOpts) {
|
|
1676
1690
|
var permissionName = toolName;
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
permissionName = sessionToolDefs[pti].permissionName;
|
|
1680
|
-
break;
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1691
|
+
var permissionToolDef = findSessionToolDef(toolName);
|
|
1692
|
+
if (permissionToolDef && permissionToolDef.permissionName) permissionName = permissionToolDef.permissionName;
|
|
1683
1693
|
return handleCanUseTool(session, permissionName, input, toolOpts);
|
|
1684
1694
|
},
|
|
1685
1695
|
onUserInputRequest: function(request, respond) {
|
package/lib/server-global-ws.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// Bound to the slug-less `/ws` endpoint by lib/server.js. Only handles the
|
|
8
8
|
// small set of messages needed to bootstrap into a project context:
|
|
9
9
|
// - ping -> pong keep-alive
|
|
10
|
+
// - home_* -> user-scoped Home preferences
|
|
10
11
|
// - browse_dir -> directory picker for the add-project modal
|
|
11
12
|
// - add_project -> register an existing directory
|
|
12
13
|
// - create_project -> make a new empty project
|
|
@@ -37,6 +38,7 @@ function attachGlobalWs(opts) {
|
|
|
37
38
|
var onAddProject = opts.onAddProject;
|
|
38
39
|
var onCreateProject = opts.onCreateProject;
|
|
39
40
|
var onCloneProject = opts.onCloneProject;
|
|
41
|
+
var onHomePreferenceMessage = opts.onHomePreferenceMessage;
|
|
40
42
|
|
|
41
43
|
function handleMessage(ws, msg) {
|
|
42
44
|
if (!msg || typeof msg !== "object") return;
|
|
@@ -46,6 +48,8 @@ function attachGlobalWs(opts) {
|
|
|
46
48
|
return;
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
if (typeof onHomePreferenceMessage === "function" && onHomePreferenceMessage(ws, msg)) return;
|
|
52
|
+
|
|
49
53
|
// --- Directory picker for the add-project modal ---
|
|
50
54
|
if (msg.type === "browse_dir") {
|
|
51
55
|
var rawPath = (msg.path || "").replace(/^~/, config.REAL_HOME);
|
|
@@ -228,6 +228,9 @@ function searchToolLabel(name, input) {
|
|
|
228
228
|
if (name === "list_project_sessions") return "Reviewing project conversations";
|
|
229
229
|
if (name === "list_projects") return "Reviewing your projects";
|
|
230
230
|
if (name === "search_project_history") return query ? "Searching a project for \u201c" + query + "\u201d" : "Searching a project";
|
|
231
|
+
if (name === "search_project_logs") return query ? "Searching project logs for \u201c" + query + "\u201d" : "Searching project logs";
|
|
232
|
+
if (name === "list_project_logs") return "Reviewing project logs";
|
|
233
|
+
if (name === "read_project_log" || name === "read_project_log_revision" || name === "project_log_history") return "Reading a project log";
|
|
231
234
|
return "Using " + (cleanActivityText(name, 64) || "a workspace tool");
|
|
232
235
|
}
|
|
233
236
|
|
package/lib/server-home-chat.js
CHANGED
|
@@ -204,7 +204,12 @@ function attachHomeChat(deps) {
|
|
|
204
204
|
|
|
205
205
|
var homeModels = attachHomeModels({ users: users, mates: mates, projects: projects, sendMessage: sendMessage });
|
|
206
206
|
var homeDebates = attachHomeDebates({ mates: mates, findMateProject: findMateProject, ownsSession: ownsSession, sessionReference: sessionReference, sendMessage: sendMessage });
|
|
207
|
-
var homeClaySessionLinks = attachHomeClaySessionLinks({
|
|
207
|
+
var homeClaySessionLinks = attachHomeClaySessionLinks({
|
|
208
|
+
projects: projects,
|
|
209
|
+
workspaceQueryService: deps.workspaceQueryService,
|
|
210
|
+
projectLogsService: deps.projectLogsService,
|
|
211
|
+
sendMessage: sendMessage,
|
|
212
|
+
});
|
|
208
213
|
|
|
209
214
|
function sendHistory(ws, found, session, requestId) {
|
|
210
215
|
if (ws.readyState !== 1) return;
|
|
@@ -383,7 +388,7 @@ function attachHomeChat(deps) {
|
|
|
383
388
|
|
|
384
389
|
function handleMessage(ws, msg) {
|
|
385
390
|
if (!msg || typeof msg.type !== "string") return false;
|
|
386
|
-
if (msg.type !== "home_debates_list" && msg.type !== "home_clay_ask" && msg.type !== "home_clay_session_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") {
|
|
391
|
+
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") {
|
|
387
392
|
return false;
|
|
388
393
|
}
|
|
389
394
|
|
|
@@ -402,6 +407,7 @@ function attachHomeChat(deps) {
|
|
|
402
407
|
if (users.isMultiUser() && !userId) {
|
|
403
408
|
if (msg.type === "home_debates_list") sendMessage(ws, { type: "home_debates_state", requestId: msg.requestId || null, status: "error", debates: [], error: "Sign in to load your debates." });
|
|
404
409
|
else if (msg.type === "home_clay_session_resolve") sendMessage(ws, { type: "home_clay_session_target", requestId: msg.requestId || null, sessionRef: msg.sessionRef || null, status: "error", error: "Sign in to open this session." });
|
|
410
|
+
else if (msg.type === "home_clay_log_resolve") sendMessage(ws, { type: "home_clay_log_target", requestId: msg.requestId || null, ref: msg.ref || null, status: "error", error: "Sign in to open this log." });
|
|
405
411
|
else if (msg.type === "home_mate_models_get" || msg.type === "home_mate_model_set") homeModels.sendAccessError(ws, msg, "Not authenticated.");
|
|
406
412
|
else sendError(ws, msg.mateId || null, "Not authenticated.", msg.requestId || null, msg.sessionId || null);
|
|
407
413
|
return true;
|
|
@@ -410,6 +416,7 @@ function attachHomeChat(deps) {
|
|
|
410
416
|
if (homeDebates.handle(ws, userId, msg)) return true;
|
|
411
417
|
if (msg.type === "home_clay_ask") { homeClayEntry.start(ws, userId, msg); return true; }
|
|
412
418
|
if (msg.type === "home_clay_session_resolve") { homeClaySessionLinks.resolve(ws, msg); return true; }
|
|
419
|
+
if (msg.type === "home_clay_log_resolve") { homeClaySessionLinks.resolveLog(ws, msg); return true; }
|
|
413
420
|
|
|
414
421
|
if (msg.type === "home_mate_debate_plan") {
|
|
415
422
|
homeDebate.start(ws, userId, msg);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
function attachHomeClayEntry(ctx) {
|
|
4
4
|
function providerPrompt(text) {
|
|
5
|
-
return "This conversation began in Clay Studio's global search. Respond as Clay. When the user is trying to locate
|
|
5
|
+
return "This conversation began in Clay Studio's global search. Respond as Clay. When the user is trying to locate a conversation, use search_workspace_history before any other investigation. When the user asks about a project's prior work, decisions, defects, status, rationale, or unfinished work, identify the project and search_project_logs before answering; use conversation history as additional evidence when useful. Search by meaning, related terms, and context, using at most three focused search passes. Do not use Bash, filesystem scans, or generic agents to search Clay history. Give the user useful findings promptly, and do not mention tools or internal search mechanics. Do not force a workspace or Logs search when the request is a general question or task.\n\nUser request:\n" + text;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
function findExisting(manager, userId, requestId) {
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
// Resolve rendered Mate session references through an exact
|
|
1
|
+
// Resolve rendered Mate session and Project Log references through an exact
|
|
2
|
+
// owner-bound source session.
|
|
2
3
|
|
|
3
4
|
function attachHomeClaySessionLinks(ctx) {
|
|
4
|
-
function
|
|
5
|
+
function source(ws, msg) {
|
|
5
6
|
var tap = msg.surface === "search" ? ws._searchClayTap : ws._homeChatTap;
|
|
6
7
|
var sourceProject = tap && tap.mateSlug ? ctx.projects.get(tap.mateSlug) : null;
|
|
7
8
|
var sourceManager = sourceProject && sourceProject.getSessionManager ? sourceProject.getSessionManager() : null;
|
|
8
9
|
var sourceSession = sourceManager && tap ? sourceManager.sessions.get(tap.sessionId) : null;
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
return { tap: tap, project: sourceProject, session: sourceSession };
|
|
11
|
+
}
|
|
12
|
+
|
|
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 })
|
|
11
17
|
: null;
|
|
12
18
|
var payload = { type: "home_clay_session_target", requestId: msg.requestId || null, sessionRef: msg.sessionRef || null, status: "error" };
|
|
13
19
|
try {
|
|
@@ -20,7 +26,30 @@ function attachHomeClaySessionLinks(ctx) {
|
|
|
20
26
|
ctx.sendMessage(ws, payload);
|
|
21
27
|
}
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
function resolveLog(ws, msg) {
|
|
30
|
+
var selected = source(ws, msg);
|
|
31
|
+
var status = selected.project && selected.project.getStatus ? selected.project.getStatus() : null;
|
|
32
|
+
var bound = ctx.projectLogsService && selected.session && status
|
|
33
|
+
? ctx.projectLogsService.bindMate({
|
|
34
|
+
projectSlug: selected.tap.mateSlug,
|
|
35
|
+
projectOwnerId: status.projectOwnerId || null,
|
|
36
|
+
isMate: true,
|
|
37
|
+
mateId: selected.tap.mateId,
|
|
38
|
+
session: selected.session,
|
|
39
|
+
})
|
|
40
|
+
: null;
|
|
41
|
+
var payload = { type: "home_clay_log_target", requestId: msg.requestId || null, ref: msg.ref || null, status: "error" };
|
|
42
|
+
try {
|
|
43
|
+
if (!bound) throw new Error("The source conversation is no longer available.");
|
|
44
|
+
payload.target = bound.resolveLogNavigation({ ref: msg.ref });
|
|
45
|
+
payload.status = "ready";
|
|
46
|
+
} catch (error) {
|
|
47
|
+
payload.error = error.message || "Log not available.";
|
|
48
|
+
}
|
|
49
|
+
ctx.sendMessage(ws, payload);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { resolve: resolve, resolveLog: resolveLog };
|
|
24
53
|
}
|
|
25
54
|
|
|
26
55
|
module.exports = { attachHomeClaySessionLinks: attachHomeClaySessionLinks };
|