clay-server 4.0.0-beta.17 → 4.0.0-beta.19
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 +8 -8
- package/lib/daemon.js +42 -10
- package/lib/git-cli.js +1 -0
- package/lib/os-users.js +3 -1
- package/lib/project-log-context.js +24 -23
- package/lib/project-logs-mcp-server.js +10 -4
- package/lib/project-logs-service.js +29 -0
- package/lib/project-logs.js +5 -1
- package/lib/project-sessions.js +5 -1
- package/lib/project.js +1 -1
- package/lib/public/css/command-palette.css +19 -9
- package/lib/public/css/sidebar.css +11 -0
- 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-home-hub.js +19 -7
- package/lib/public/modules/app-projects.js +5 -3
- 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/markdown.js +2 -0
- package/lib/public/modules/project-logs.js +7 -0
- package/lib/public/modules/sidebar-session-hierarchy.js +16 -8
- 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 +11 -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 +19 -10
- package/lib/session-hygiene.js +3 -0
- package/lib/users.js +16 -16
- package/lib/worktree.js +25 -38
- package/lib/ws-schema.js +2 -0
- package/package.json +1 -1
|
@@ -417,13 +417,15 @@ export function switchProject(slug) {
|
|
|
417
417
|
if (!slug) return;
|
|
418
418
|
document.cookie = "clay_last_project=" + encodeURIComponent(slug) + "; Path=/; SameSite=Strict; Max-Age=31536000";
|
|
419
419
|
var st = store.snap();
|
|
420
|
+
var targetWsPath = "/p/" + slug + "/ws";
|
|
421
|
+
var alreadyInProject = slug === st.currentSlug && st.wsPath === targetWsPath;
|
|
420
422
|
var wasDm = st.dmMode;
|
|
421
423
|
var wasMate = st.dmMode && st.dmTargetUser && st.dmTargetUser.isMate;
|
|
422
424
|
if (st.dmMode) exitDmMode(wasMate);
|
|
423
425
|
closeWhatsNewArticle();
|
|
424
426
|
if (isHomeHubVisible()) {
|
|
425
427
|
hideHomeHub();
|
|
426
|
-
if (
|
|
428
|
+
if (alreadyInProject) {
|
|
427
429
|
if (document.documentElement.classList.contains("pwa-standalone")) {
|
|
428
430
|
history.replaceState(null, "", "/p/" + slug + "/");
|
|
429
431
|
} else {
|
|
@@ -432,7 +434,7 @@ export function switchProject(slug) {
|
|
|
432
434
|
return;
|
|
433
435
|
}
|
|
434
436
|
}
|
|
435
|
-
if (
|
|
437
|
+
if (alreadyInProject) {
|
|
436
438
|
var ws = getWs();
|
|
437
439
|
if (wasDm && ws && ws.readyState === 1) {
|
|
438
440
|
ws.send(JSON.stringify({ type: "switch_session", id: store.get('activeSessionId') }));
|
|
@@ -446,7 +448,7 @@ export function switchProject(slug) {
|
|
|
446
448
|
resetScheduler(slug);
|
|
447
449
|
store.set({ currentSlug: slug });
|
|
448
450
|
store.set({ basePath: "/p/" + slug + "/" });
|
|
449
|
-
store.set({ wsPath:
|
|
451
|
+
store.set({ wsPath: targetWsPath });
|
|
450
452
|
if (document.documentElement.classList.contains("pwa-standalone")) {
|
|
451
453
|
history.replaceState(null, "", "/p/" + slug + "/");
|
|
452
454
|
} else {
|
|
@@ -105,7 +105,7 @@ export function addAssistantCopyHandler(row, rawText) {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
row.addEventListener("click", function (event) {
|
|
108
|
-
if (event.target.closest("a, pre, code")) return;
|
|
108
|
+
if (event.target.closest("a, button, pre, code")) return;
|
|
109
109
|
var selection = window.getSelection();
|
|
110
110
|
if (selection && selection.toString().length > 0) return;
|
|
111
111
|
if (!primed) {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export function parseClayLogReferences(text) {
|
|
2
|
+
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;
|
|
3
|
+
var results = [];
|
|
4
|
+
var match;
|
|
5
|
+
while ((match = pattern.exec(text || ""))) {
|
|
6
|
+
results.push({
|
|
7
|
+
start: match.index,
|
|
8
|
+
end: pattern.lastIndex,
|
|
9
|
+
prefix: match[3] || "",
|
|
10
|
+
ref: match[1] || match[4],
|
|
11
|
+
label: match[2] ? match[2].trim() : "",
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
return results;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isExactClayLogReference(text) {
|
|
18
|
+
return /^log:[A-Za-z0-9_-]{24}$/.test((text || "").trim());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createLogLink(ref, labelText) {
|
|
22
|
+
var button = document.createElement("button");
|
|
23
|
+
button.type = "button";
|
|
24
|
+
button.className = "clayos-log-link";
|
|
25
|
+
button.contentEditable = "false";
|
|
26
|
+
button.dataset.logRef = ref;
|
|
27
|
+
button.setAttribute("aria-label", "Open Project Log" + (labelText ? " " + labelText : ""));
|
|
28
|
+
var label = document.createElement("span");
|
|
29
|
+
label.textContent = "Log";
|
|
30
|
+
button.appendChild(label);
|
|
31
|
+
if (labelText) {
|
|
32
|
+
var meta = document.createElement("small");
|
|
33
|
+
meta.textContent = labelText;
|
|
34
|
+
button.appendChild(meta);
|
|
35
|
+
}
|
|
36
|
+
return button;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function enhanceClayLogLinks(root) {
|
|
40
|
+
var codeNodes = root.querySelectorAll("code");
|
|
41
|
+
for (var codeIndex = 0; codeIndex < codeNodes.length; codeIndex++) {
|
|
42
|
+
var code = codeNodes[codeIndex];
|
|
43
|
+
var codeText = (code.textContent || "").trim();
|
|
44
|
+
if (code.closest("pre") || !isExactClayLogReference(codeText)) continue;
|
|
45
|
+
code.parentNode.replaceChild(createLogLink(codeText, ""), code);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
49
|
+
var nodes = [];
|
|
50
|
+
var node;
|
|
51
|
+
while ((node = walker.nextNode())) {
|
|
52
|
+
if (!node.parentElement || node.parentElement.closest("code, pre, a, button")) continue;
|
|
53
|
+
if (parseClayLogReferences(node.nodeValue).length) nodes.push(node);
|
|
54
|
+
}
|
|
55
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
56
|
+
var text = nodes[i].nodeValue || "";
|
|
57
|
+
var matches = parseClayLogReferences(text);
|
|
58
|
+
var fragment = document.createDocumentFragment();
|
|
59
|
+
var offset = 0;
|
|
60
|
+
for (var j = 0; j < matches.length; j++) {
|
|
61
|
+
var match = matches[j];
|
|
62
|
+
fragment.appendChild(document.createTextNode(text.slice(offset, match.start)));
|
|
63
|
+
if (match.prefix) fragment.appendChild(document.createTextNode(match.prefix));
|
|
64
|
+
fragment.appendChild(createLogLink(match.ref, match.label));
|
|
65
|
+
offset = match.end;
|
|
66
|
+
}
|
|
67
|
+
fragment.appendChild(document.createTextNode(text.slice(offset)));
|
|
68
|
+
nodes[i].parentNode.replaceChild(fragment, nodes[i]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -6,6 +6,7 @@ import { showHomeHub, minimizeHomeHub } from './app-home-hub.js';
|
|
|
6
6
|
import { openHomeConversation } from './home-mate-chat.js';
|
|
7
7
|
import { startSearchClayChat, attachSearchClayChat, detachSearchClayChat, getSearchClayChatSummary } from './search-clay-chat.js';
|
|
8
8
|
import { getWs } from './ws-ref.js';
|
|
9
|
+
import { openProjectLog } from './project-logs.js';
|
|
9
10
|
|
|
10
11
|
function formatRelativeDate(ts) {
|
|
11
12
|
if (!ts) return "";
|
|
@@ -27,6 +28,7 @@ var ctx;
|
|
|
27
28
|
var paletteEl = null, inputEl = null, resultsEl = null, footerEl = null;
|
|
28
29
|
var activeIndex = -1, items = [], debounceTimer = null, abortCtrl = null;
|
|
29
30
|
var pendingNav = null, cachedHomeData = [], cachedVersion = null;
|
|
31
|
+
var pendingLogNav = null;
|
|
30
32
|
var searchResults = [], searchQuery = "", searchPending = false, chatMode = false;
|
|
31
33
|
|
|
32
34
|
export function initCommandPalette(value) {
|
|
@@ -52,43 +54,62 @@ export function initCommandPalette(value) {
|
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
function handleClaySessionLinkClick(event) {
|
|
55
|
-
var link = event.target.closest(".clayos-session-link");
|
|
57
|
+
var link = event.target.closest(".clayos-session-link, .clayos-log-link");
|
|
56
58
|
if (!link || link.classList.contains("is-loading")) return;
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
event.stopPropagation();
|
|
61
|
+
if (link.classList.contains("clayos-log-link") && !link.closest(".search-clay-chat, #home-mate-chat")) {
|
|
62
|
+
openProjectLog(link.dataset.logRef);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
57
65
|
var ws = getWs();
|
|
58
66
|
if (!ws || ws.readyState !== 1) {
|
|
59
67
|
link.classList.add("is-error");
|
|
60
|
-
link.title = "Reconnect to open this
|
|
68
|
+
link.title = "Reconnect to open this item";
|
|
61
69
|
return;
|
|
62
70
|
}
|
|
63
|
-
var
|
|
64
|
-
|
|
71
|
+
var isLog = link.classList.contains("clayos-log-link");
|
|
72
|
+
var requestId = "clayos-" + (isLog ? "log-" : "session-") + Date.now() + "-" + Math.random().toString(36).slice(2, 9);
|
|
73
|
+
if (isLog) link.dataset.logRequest = requestId;
|
|
74
|
+
else link.dataset.sessionRequest = requestId;
|
|
65
75
|
link.classList.remove("is-error");
|
|
66
76
|
link.classList.add("is-loading");
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
var request = { requestId: requestId, surface: link.closest(".search-clay-chat") ? "search" : "home" };
|
|
78
|
+
if (isLog) {
|
|
79
|
+
request.type = "home_clay_log_resolve";
|
|
80
|
+
request.ref = link.dataset.logRef;
|
|
81
|
+
} else {
|
|
82
|
+
request.type = "home_clay_session_resolve";
|
|
83
|
+
request.sessionRef = link.dataset.sessionRef;
|
|
84
|
+
}
|
|
85
|
+
ws.send(JSON.stringify(request));
|
|
73
86
|
}
|
|
74
87
|
|
|
75
88
|
export function handleClaySessionTarget(msg) {
|
|
76
|
-
if (!msg || msg.type !== "home_clay_session_target") return false;
|
|
77
|
-
var
|
|
89
|
+
if (!msg || (msg.type !== "home_clay_session_target" && msg.type !== "home_clay_log_target")) return false;
|
|
90
|
+
var isLog = msg.type === "home_clay_log_target";
|
|
91
|
+
var links = document.querySelectorAll(isLog ? ".clayos-log-link[data-log-request]" : ".clayos-session-link[data-session-request]");
|
|
78
92
|
var link = null;
|
|
79
|
-
for (var i = 0; i < links.length; i++)
|
|
93
|
+
for (var i = 0; i < links.length; i++) {
|
|
94
|
+
if ((isLog ? links[i].dataset.logRequest : links[i].dataset.sessionRequest) === msg.requestId) link = links[i];
|
|
95
|
+
}
|
|
80
96
|
if (link) {
|
|
81
97
|
link.classList.remove("is-loading");
|
|
82
|
-
delete link.dataset.
|
|
98
|
+
if (isLog) delete link.dataset.logRequest;
|
|
99
|
+
else delete link.dataset.sessionRequest;
|
|
83
100
|
}
|
|
84
101
|
if (msg.status !== "ready" || !msg.target) {
|
|
85
102
|
if (link) {
|
|
86
103
|
link.classList.add("is-error");
|
|
87
|
-
link.title = msg.error || "
|
|
104
|
+
link.title = msg.error || "Item not available";
|
|
88
105
|
}
|
|
89
106
|
return true;
|
|
90
107
|
}
|
|
91
108
|
closeCommandPalette();
|
|
109
|
+
if (isLog) {
|
|
110
|
+
navigateToLog(msg.target);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
92
113
|
if (msg.target.isMate && msg.target.mateId && msg.target.homeSessionId) {
|
|
93
114
|
showHomeHub();
|
|
94
115
|
openHomeConversation(msg.target.mateId, msg.target.homeSessionId);
|
|
@@ -129,6 +150,11 @@ export function closeCommandPalette() {
|
|
|
129
150
|
export function setPaletteVersion(version) { cachedVersion = version; }
|
|
130
151
|
|
|
131
152
|
export function handlePaletteSessionSwitch() {
|
|
153
|
+
if (pendingLogNav && ctx.currentSlug && ctx.currentSlug() === pendingLogNav.projectSlug) {
|
|
154
|
+
var logNav = pendingLogNav;
|
|
155
|
+
pendingLogNav = null;
|
|
156
|
+
openProjectLog(logNav.ref);
|
|
157
|
+
}
|
|
132
158
|
if (!pendingNav) return;
|
|
133
159
|
var nav = pendingNav;
|
|
134
160
|
pendingNav = null;
|
|
@@ -138,6 +164,17 @@ export function handlePaletteSessionSwitch() {
|
|
|
138
164
|
}
|
|
139
165
|
}
|
|
140
166
|
|
|
167
|
+
function navigateToLog(target) {
|
|
168
|
+
closeCommandPalette();
|
|
169
|
+
minimizeHomeHub();
|
|
170
|
+
if (ctx.currentSlug && ctx.currentSlug() === target.projectSlug) {
|
|
171
|
+
openProjectLog(target.ref);
|
|
172
|
+
} else {
|
|
173
|
+
pendingLogNav = { projectSlug: target.projectSlug, ref: target.ref };
|
|
174
|
+
ctx.switchProject(target.projectSlug);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
141
178
|
function buildDOM() {
|
|
142
179
|
paletteEl = document.createElement("div");
|
|
143
180
|
paletteEl.className = "cmd-palette hidden";
|
|
@@ -2,6 +2,7 @@ import { copyToClipboard, escapeHtml } from './utils.js';
|
|
|
2
2
|
import { refreshIcons } from './icons.js';
|
|
3
3
|
import { getMermaidThemeVars } from './theme.js';
|
|
4
4
|
import { store } from './store.js';
|
|
5
|
+
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
5
6
|
|
|
6
7
|
// Initialize markdown parser
|
|
7
8
|
marked.use({ gfm: true, breaks: false });
|
|
@@ -89,6 +90,7 @@ export function enhanceClaySessionLinks(root) {
|
|
|
89
90
|
fragment.appendChild(document.createTextNode(text.slice(offset)));
|
|
90
91
|
nodes[i].parentNode.replaceChild(fragment, nodes[i]);
|
|
91
92
|
}
|
|
93
|
+
enhanceClayLogLinks(root);
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
/**
|
|
@@ -336,6 +336,13 @@ export function openProjectLogs() {
|
|
|
336
336
|
requestList();
|
|
337
337
|
}
|
|
338
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
|
+
|
|
339
346
|
export function closeProjectLogs() {
|
|
340
347
|
if (!store.get('projectLogsOpen')) return;
|
|
341
348
|
if (panel) {
|
|
@@ -84,6 +84,8 @@ export function renderDesktopDriverHierarchy(root, renderSession, rerender, matc
|
|
|
84
84
|
var key = "driver:" + root.driver.id;
|
|
85
85
|
var isExpanded = expanded("desktop", key, root.workers, matchIds);
|
|
86
86
|
var childrenId = "session-workers-" + root.driver.id;
|
|
87
|
+
var header = document.createElement("div");
|
|
88
|
+
header.className = "session-driver-header";
|
|
87
89
|
var row = renderSession(root.driver);
|
|
88
90
|
row.classList.add("session-driver-item");
|
|
89
91
|
var control = document.createElement("button");
|
|
@@ -93,20 +95,26 @@ export function renderDesktopDriverHierarchy(root, renderSession, rerender, matc
|
|
|
93
95
|
control.setAttribute("aria-controls", childrenId);
|
|
94
96
|
control.setAttribute("aria-label", (isExpanded ? "Collapse" : "Expand") + " Workers for " + (root.driver.title || "New Session"));
|
|
95
97
|
control.innerHTML = iconHtml("chevron-right");
|
|
96
|
-
control.addEventListener("click", function (event) {
|
|
97
|
-
event.preventDefault();
|
|
98
|
-
event.stopPropagation();
|
|
99
|
-
toggle("desktop", key, isExpanded, rerender);
|
|
100
|
-
});
|
|
101
|
-
row.insertBefore(control, row.firstChild);
|
|
102
|
-
wrapper.appendChild(row);
|
|
103
|
-
|
|
104
98
|
var children = document.createElement("div");
|
|
105
99
|
children.id = childrenId;
|
|
106
100
|
children.className = "session-worker-children";
|
|
107
101
|
children.setAttribute("role", "group");
|
|
108
102
|
children.setAttribute("aria-label", "Workers for " + (root.driver.title || "New Session"));
|
|
109
103
|
children.hidden = !isExpanded;
|
|
104
|
+
control.addEventListener("click", function (event) {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
event.stopPropagation();
|
|
107
|
+
var nextExpanded = !isExpanded;
|
|
108
|
+
expansionBySurface.desktop.set(key, nextExpanded);
|
|
109
|
+
control.setAttribute("aria-expanded", String(nextExpanded));
|
|
110
|
+
control.setAttribute("aria-label", (nextExpanded ? "Collapse" : "Expand") + " Workers for " + (root.driver.title || "New Session"));
|
|
111
|
+
children.hidden = !nextExpanded;
|
|
112
|
+
isExpanded = nextExpanded;
|
|
113
|
+
});
|
|
114
|
+
header.appendChild(control);
|
|
115
|
+
header.appendChild(row);
|
|
116
|
+
wrapper.appendChild(header);
|
|
117
|
+
|
|
110
118
|
var current = currentWorkerIds();
|
|
111
119
|
for (var i = 0; i < root.workers.length; i++) {
|
|
112
120
|
if (!visibleWorker(root.workers[i], root.driver, matchIds)) continue;
|
|
@@ -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 and Mate -> the same user-scoped handlers as project sockets
|
|
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,8 @@ 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;
|
|
42
|
+
var onAppMessage = opts.onAppMessage;
|
|
40
43
|
|
|
41
44
|
function handleMessage(ws, msg) {
|
|
42
45
|
if (!msg || typeof msg !== "object") return;
|
|
@@ -46,6 +49,14 @@ function attachGlobalWs(opts) {
|
|
|
46
49
|
return;
|
|
47
50
|
}
|
|
48
51
|
|
|
52
|
+
// A user does not need an ordinary project before Home becomes useful.
|
|
53
|
+
// Route Mate discovery, Home chat/debates, and tools through the same
|
|
54
|
+
// handlers used by project sockets. This also lets mate_list repair an
|
|
55
|
+
// existing account whose built-ins were missing from its final storage.
|
|
56
|
+
if (typeof onAppMessage === "function" && onAppMessage(ws, msg)) return;
|
|
57
|
+
|
|
58
|
+
if (typeof onHomePreferenceMessage === "function" && onHomePreferenceMessage(ws, msg)) return;
|
|
59
|
+
|
|
49
60
|
// --- Directory picker for the add-project modal ---
|
|
50
61
|
if (msg.type === "browse_dir") {
|
|
51
62
|
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 };
|