clay-server 4.0.0-beta.9 → 4.0.0
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/capsule-display-floor.js +51 -0
- package/lib/capsule-frame-server.js +255 -0
- package/lib/capsule-mate-turn.js +105 -0
- package/lib/capsule-pig-logic.js +300 -0
- package/lib/capsule-server-runtimes.js +39 -0
- package/lib/capsules/pig/display.js +198 -0
- package/lib/capsules/pig/manifest.json +9 -0
- package/lib/capsules/pig/ui.json +84 -0
- package/lib/config.js +25 -1
- package/lib/daemon-projects.js +13 -8
- package/lib/daemon.js +127 -14
- package/lib/git-cli.js +95 -1
- package/lib/knowledge-import.js +436 -0
- package/lib/knowledge-record-store.js +188 -0
- package/lib/knowledge-search.js +240 -0
- package/lib/mate-knowledge-mcp-server.js +151 -0
- package/lib/mate-knowledge-migration.js +283 -0
- package/lib/mate-knowledge-service.js +488 -0
- package/lib/mate-knowledge-sync.js +482 -0
- package/lib/migrate-single-user.js +2 -0
- package/lib/notes-lifecycle.js +149 -0
- package/lib/notes.js +60 -3
- package/lib/os-users.js +3 -1
- package/lib/project-capsule-catalog.js +9 -1
- package/lib/project-capsule-turn.js +96 -0
- package/lib/project-connection.js +15 -1
- package/lib/project-file-history.js +223 -0
- package/lib/project-file-watch.js +77 -49
- package/lib/project-filesystem.js +31 -220
- package/lib/project-http.js +15 -1
- package/lib/project-knowledge.js +22 -0
- package/lib/project-log-context.js +172 -0
- package/lib/project-log-feedback-delivery.js +118 -0
- package/lib/project-logs-comments.js +137 -0
- package/lib/project-logs-context-state.js +42 -0
- package/lib/project-logs-mcp-server.js +328 -0
- package/lib/project-logs-query.js +185 -0
- package/lib/project-logs-root.js +75 -0
- package/lib/project-logs-schema.js +259 -0
- package/lib/project-logs-service.js +463 -0
- package/lib/project-logs-snapshot.js +205 -0
- package/lib/project-logs-store.js +495 -0
- package/lib/project-logs-versioning.js +132 -0
- package/lib/project-logs.js +400 -0
- package/lib/project-mate-interaction.js +14 -0
- package/lib/project-mate-knowledge.js +110 -0
- package/lib/project-memory.js +22 -0
- package/lib/project-message-delivery.js +70 -0
- package/lib/project-pair-lifecycle.js +460 -0
- package/lib/project-request-access.js +66 -0
- package/lib/project-session-handoff.js +1 -1
- package/lib/project-session-notes.js +64 -13
- package/lib/project-session-pair.js +159 -146
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +58 -13
- package/lib/project-user-message.js +53 -4
- package/lib/project-worker-permission.js +430 -0
- package/lib/project-worker-proposal.js +208 -62
- package/lib/project.js +185 -6
- package/lib/public/app.js +63 -35
- package/lib/public/css/admin.css +2 -2
- package/lib/public/css/capsule-ui.css +38 -0
- package/lib/public/css/command-palette.css +19 -9
- package/lib/public/css/git-placard.css +154 -0
- package/lib/public/css/home-hub.css +4 -0
- package/lib/public/css/home-session-actions.css +66 -0
- package/lib/public/css/home-sidebar.css +66 -0
- package/lib/public/css/input.css +84 -3
- package/lib/public/css/messages.css +40 -1
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/notifications-center.css +67 -0
- package/lib/public/css/pane.css +97 -10
- package/lib/public/css/project-logs.css +198 -0
- package/lib/public/css/scheduler.css +1 -1
- package/lib/public/css/session-actions.css +16 -101
- package/lib/public/css/sidebar.css +103 -0
- package/lib/public/css/sticky-notes.css +237 -229
- package/lib/public/css/worker-proposal.css +36 -3
- package/lib/public/index.html +43 -11
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +124 -6
- package/lib/public/modules/app-home-hub.js +12 -8
- package/lib/public/modules/app-message-cards.js +2 -1
- package/lib/public/modules/app-messages.js +92 -11
- package/lib/public/modules/app-misc.js +2 -14
- package/lib/public/modules/app-notifications.js +11 -7
- package/lib/public/modules/app-panels.js +3 -2
- package/lib/public/modules/app-projects.js +56 -18
- package/lib/public/modules/app-rendering.js +1 -0
- package/lib/public/modules/capsule-preference.js +44 -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/context-sources.js +0 -3
- package/lib/public/modules/git-agent-sessions.js +85 -0
- package/lib/public/modules/git-panel.js +111 -84
- package/lib/public/modules/git-placard.js +161 -0
- package/lib/public/modules/home-chat-empty-state.js +39 -0
- package/lib/public/modules/home-chat-identity.js +9 -0
- package/lib/public/modules/home-conversations-sheet.js +102 -48
- package/lib/public/modules/home-mate-chat.js +15 -35
- package/lib/public/modules/home-session-actions.js +6 -0
- package/lib/public/modules/home-sidebar-chat-list.js +101 -41
- package/lib/public/modules/home-sidebar.js +7 -0
- package/lib/public/modules/home-surface-boot.js +7 -3
- package/lib/public/modules/home-tool-frame.js +182 -0
- package/lib/public/modules/home-tools.js +138 -1
- package/lib/public/modules/input.js +38 -7
- package/lib/public/modules/markdown.js +2 -0
- package/lib/public/modules/mate-sidebar.js +0 -8
- package/lib/public/modules/message-delivery.js +97 -0
- package/lib/public/modules/paste-modal.js +84 -0
- package/lib/public/modules/project-activation.js +45 -0
- package/lib/public/modules/project-logs-render.js +405 -0
- package/lib/public/modules/project-logs.js +461 -0
- package/lib/public/modules/project-removal-target.js +26 -0
- package/lib/public/modules/scheduler.js +17 -3
- package/lib/public/modules/session-actions.js +29 -105
- package/lib/public/modules/session-hierarchy.js +54 -0
- package/lib/public/modules/sidebar-mobile.js +40 -15
- package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
- package/lib/public/modules/sidebar-sessions.js +77 -11
- package/lib/public/modules/sidebar.js +17 -5
- package/lib/public/modules/split-group-helpers.js +8 -0
- package/lib/public/modules/split-pair-ui.js +15 -4
- package/lib/public/modules/split-view.js +5 -2
- package/lib/public/modules/split-worker-runtime.js +24 -0
- package/lib/public/modules/sticky-note-markdown.js +5 -0
- package/lib/public/modules/sticky-notes-browser.js +326 -0
- package/lib/public/modules/sticky-notes-card.js +360 -0
- package/lib/public/modules/sticky-notes-editor.js +291 -0
- package/lib/public/modules/sticky-notes-shared.js +90 -0
- package/lib/public/modules/sticky-notes.js +170 -946
- package/lib/public/modules/thinking-lifecycle.js +175 -0
- package/lib/public/modules/thinking-summary.js +28 -0
- package/lib/public/modules/thinking-view.js +75 -0
- package/lib/public/modules/tool-palette-order.js +143 -0
- package/lib/public/modules/tool-palette-overlays.js +159 -0
- package/lib/public/modules/tool-palette.js +21 -188
- package/lib/public/modules/tools.js +31 -123
- package/lib/public/modules/update-snooze.js +169 -0
- package/lib/public/modules/user-settings.js +4 -0
- package/lib/public/modules/worker-pane-lock.js +176 -0
- package/lib/public/modules/worker-proposal-state.js +16 -0
- package/lib/public/modules/worker-proposal.js +49 -20
- package/lib/public/style.css +3 -1
- package/lib/sdk-bridge.js +135 -46
- package/lib/sdk-message-processor.js +36 -7
- package/lib/server-experimental-settings.js +49 -0
- package/lib/server-global-ws.js +11 -0
- package/lib/server-home-chat-events.js +31 -1
- package/lib/server-home-chat.js +27 -4
- package/lib/server-home-clay-entry.js +1 -1
- package/lib/server-home-clay-session-links.js +34 -5
- package/lib/server-settings.js +5 -0
- package/lib/server-tools.js +127 -9
- package/lib/server.js +137 -22
- package/lib/session-driver-eligibility.js +38 -0
- package/lib/session-driver-orchestration.js +31 -0
- package/lib/session-hygiene.js +3 -0
- package/lib/session-notes-mcp-server.js +25 -5
- package/lib/session-pair-factory.js +247 -0
- package/lib/session-pair-mcp-server.js +46 -8
- package/lib/session-pair-prompts.js +79 -0
- package/lib/session-pair-turn-control.js +153 -0
- package/lib/session-provenance.js +119 -0
- package/lib/session-spawn-mcp-server.js +1 -1
- package/lib/session-split-groups.js +4 -1
- package/lib/session-title-policy.js +60 -0
- package/lib/session-visibility.js +41 -0
- package/lib/sessions.js +57 -10
- package/lib/tools-registry.js +89 -10
- package/lib/update-snooze.js +437 -0
- package/lib/users-experimental-preferences.js +30 -0
- package/lib/users-permissions.js +6 -6
- package/lib/users.js +22 -16
- package/lib/workspace-query-access.js +102 -0
- package/lib/workspace-query-service.js +24 -17
- package/lib/worktree.js +25 -38
- package/lib/ws-schema.js +38 -7
- package/lib/yoke/adapters/claude-worker.js +16 -6
- package/lib/yoke/adapters/claude.js +12 -0
- package/lib/yoke/adapters/codex.js +24 -14
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { escapeHtml } from './utils.js';
|
|
2
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
import { addToMessages, scrollToBottom } from './chat-render-runtime.js';
|
|
5
|
+
import { prepareThinkingView, bindThinkingView, updateThinkingView, beginThinkingView, setThinkingViewLive, finishThinkingView } from './thinking-view.js';
|
|
6
|
+
|
|
7
|
+
function getThinkingState() {
|
|
8
|
+
return store.get('thinkingState') || null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function setThinkingState(state) {
|
|
12
|
+
store.set({ thinkingState: state });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function updateThinkingState(state, patch) {
|
|
16
|
+
var next = Object.assign({}, state, patch);
|
|
17
|
+
setThinkingState(next);
|
|
18
|
+
return next;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isMateThinking() {
|
|
22
|
+
var target = store.get('dmTargetUser');
|
|
23
|
+
return !!(store.get('dmMode') && target && target.isMate);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function maybeScrollToBottom() {
|
|
27
|
+
if (!store.get('replayingHistory')) scrollToBottom();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function createThinkingElement() {
|
|
31
|
+
var el = document.createElement("div");
|
|
32
|
+
el.className = "thinking-item";
|
|
33
|
+
if (isMateThinking()) {
|
|
34
|
+
var target = store.get('dmTargetUser');
|
|
35
|
+
var mateName = (target && target.displayName) || "Mate";
|
|
36
|
+
var mateAvatar = document.body.dataset.mateAvatarUrl || "";
|
|
37
|
+
el.classList.add("mate-thinking");
|
|
38
|
+
el.innerHTML =
|
|
39
|
+
'<img class="dm-bubble-avatar dm-bubble-avatar-mate" src="' + escapeHtml(mateAvatar) + '" alt="">' +
|
|
40
|
+
'<div class="dm-bubble-content">' +
|
|
41
|
+
'<div class="dm-bubble-header"><span class="dm-bubble-name">' + escapeHtml(mateName) + '</span></div>' +
|
|
42
|
+
'<div class="mate-thinking-dots mate-thinking-activity"><span></span><span></span><span></span></div>' +
|
|
43
|
+
'<div class="thinking-header" style="display:none">' +
|
|
44
|
+
'<span class="thinking-chevron">' + iconHtml("chevron-right") + '</span>' +
|
|
45
|
+
'<span class="thinking-label">Thinking</span>' +
|
|
46
|
+
'<span class="thinking-duration"></span>' +
|
|
47
|
+
'<span class="thinking-spinner">' + iconHtml("loader", "icon-spin") + '</span>' +
|
|
48
|
+
'</div>' +
|
|
49
|
+
'<div class="thinking-content"></div>' +
|
|
50
|
+
'</div>';
|
|
51
|
+
} else {
|
|
52
|
+
el.innerHTML =
|
|
53
|
+
'<div class="thinking-header">' +
|
|
54
|
+
'<span class="thinking-chevron">' + iconHtml("chevron-right") + '</span>' +
|
|
55
|
+
'<span class="thinking-label">Thinking</span>' +
|
|
56
|
+
'<span class="thinking-duration"></span>' +
|
|
57
|
+
'<span class="thinking-spinner">' + iconHtml("loader", "icon-spin") + '</span>' +
|
|
58
|
+
'</div>' +
|
|
59
|
+
'<div class="thinking-content"></div>';
|
|
60
|
+
}
|
|
61
|
+
prepareThinkingView(el);
|
|
62
|
+
bindThinkingView(el);
|
|
63
|
+
addToMessages(el);
|
|
64
|
+
return el;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function showMateActivity(el, active, hasContent) {
|
|
68
|
+
if (!el.classList.contains("mate-thinking")) return;
|
|
69
|
+
var activity = el.querySelector(".mate-thinking-activity");
|
|
70
|
+
var header = el.querySelector(".thinking-header");
|
|
71
|
+
if (activity) activity.style.display = active ? "" : "none";
|
|
72
|
+
if (header) header.style.display = active || !hasContent ? "none" : "";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function startThinkingSegment() {
|
|
76
|
+
var thinkingState = getThinkingState();
|
|
77
|
+
if (thinkingState && thinkingState.active) return thinkingState.el;
|
|
78
|
+
if (!thinkingState || thinkingState.ended) {
|
|
79
|
+
thinkingState = {
|
|
80
|
+
el: createThinkingElement(),
|
|
81
|
+
detailsText: "",
|
|
82
|
+
segmentText: "",
|
|
83
|
+
latestText: "",
|
|
84
|
+
active: false,
|
|
85
|
+
ended: false,
|
|
86
|
+
startTime: 0,
|
|
87
|
+
totalDuration: 0,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
thinkingState = updateThinkingState(thinkingState, {
|
|
91
|
+
active: true,
|
|
92
|
+
segmentText: "",
|
|
93
|
+
startTime: Date.now(),
|
|
94
|
+
});
|
|
95
|
+
beginThinkingView(thinkingState.el, !store.get('replayingHistory') && !isMateThinking());
|
|
96
|
+
showMateActivity(thinkingState.el, true, !!thinkingState.detailsText.trim());
|
|
97
|
+
refreshIcons();
|
|
98
|
+
maybeScrollToBottom();
|
|
99
|
+
return thinkingState.el;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function appendThinkingText(text) {
|
|
103
|
+
var thinkingState = getThinkingState();
|
|
104
|
+
if (!thinkingState || !thinkingState.active || typeof text !== "string" || !text) return;
|
|
105
|
+
var separator = !thinkingState.segmentText && thinkingState.detailsText ? "\n\n" : "";
|
|
106
|
+
var segmentText = thinkingState.segmentText + text;
|
|
107
|
+
thinkingState = updateThinkingState(thinkingState, {
|
|
108
|
+
segmentText: segmentText,
|
|
109
|
+
detailsText: thinkingState.detailsText + separator + text,
|
|
110
|
+
latestText: segmentText.trim() ? segmentText : thinkingState.latestText,
|
|
111
|
+
});
|
|
112
|
+
updateThinkingView(thinkingState.el, thinkingState.segmentText || thinkingState.latestText, thinkingState.detailsText);
|
|
113
|
+
maybeScrollToBottom();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function stopThinkingSegment(duration) {
|
|
117
|
+
var thinkingState = getThinkingState();
|
|
118
|
+
if (!thinkingState || !thinkingState.active) return;
|
|
119
|
+
var seconds = typeof duration === "number" ? duration : (Date.now() - thinkingState.startTime) / 1000;
|
|
120
|
+
thinkingState = updateThinkingState(thinkingState, {
|
|
121
|
+
totalDuration: thinkingState.totalDuration + seconds,
|
|
122
|
+
active: false,
|
|
123
|
+
});
|
|
124
|
+
finishThinkingView(thinkingState.el, thinkingState.latestText, thinkingState.detailsText);
|
|
125
|
+
thinkingState.el.querySelector(".thinking-duration").textContent = " " + thinkingState.totalDuration.toFixed(1) + "s";
|
|
126
|
+
showMateActivity(thinkingState.el, false, !!thinkingState.detailsText.trim());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function finishThinkingTurn() {
|
|
130
|
+
var thinkingState = getThinkingState();
|
|
131
|
+
if (!thinkingState) return;
|
|
132
|
+
stopThinkingSegment();
|
|
133
|
+
thinkingState = getThinkingState();
|
|
134
|
+
thinkingState = updateThinkingState(thinkingState, { ended: true });
|
|
135
|
+
finishThinkingView(thinkingState.el, thinkingState.latestText, thinkingState.detailsText);
|
|
136
|
+
showMateActivity(thinkingState.el, false, !!thinkingState.detailsText.trim());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function resetThinkingTurn() {
|
|
140
|
+
finishThinkingTurn();
|
|
141
|
+
setThinkingState(null);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function clearThinkingState() {
|
|
145
|
+
setThinkingState(null);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function saveThinkingState() {
|
|
149
|
+
return getThinkingState();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function restoreThinkingState(saved) {
|
|
153
|
+
setThinkingState(saved || null);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function resumeThinkingAfterReplay(processing) {
|
|
157
|
+
var thinkingState = getThinkingState();
|
|
158
|
+
if (!thinkingState || !thinkingState.active || !processing) return;
|
|
159
|
+
setThinkingViewLive(thinkingState.el, !isMateThinking());
|
|
160
|
+
showMateActivity(thinkingState.el, true, !!thinkingState.detailsText.trim());
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function updateThinkingTokenEstimate(estimatedTokens) {
|
|
164
|
+
var thinkingState = getThinkingState();
|
|
165
|
+
if (!thinkingState || !thinkingState.active || !thinkingState.el) return;
|
|
166
|
+
var label = thinkingState.el.querySelector(".thinking-label");
|
|
167
|
+
if (!label) return;
|
|
168
|
+
var count = estimatedTokens || 0;
|
|
169
|
+
var display = count >= 1000 ? ("~" + (Math.round(count / 100) / 10) + "k") : ("~" + count);
|
|
170
|
+
if (thinkingState.latestText.trim()) {
|
|
171
|
+
label.title = label.textContent + " · " + display + " tokens";
|
|
172
|
+
} else {
|
|
173
|
+
label.textContent = "Thinking " + display + " tokens";
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Summarize only supplied text; a heading is preferable to a prose excerpt.
|
|
2
|
+
function plainText(text) {
|
|
3
|
+
return text.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
4
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
|
|
5
|
+
.replace(/^\s*(?:#{1,6}\s+|>\s*)/gm, "")
|
|
6
|
+
.replace(/[*_`~]/g, "")
|
|
7
|
+
.replace(/\s+/g, " ").trim();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function thinkingSummary(text) {
|
|
11
|
+
if (typeof text !== "string" || !text.trim()) return "Thinking";
|
|
12
|
+
var lines = text.split("\n");
|
|
13
|
+
var heading = "";
|
|
14
|
+
for (var i = 0; i < lines.length; i++) {
|
|
15
|
+
if (/^\s*(?:#{1,6}\s+|\*\*|__)/.test(lines[i])) {
|
|
16
|
+
var candidate = plainText(lines[i]);
|
|
17
|
+
if (candidate) heading = candidate;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
var paragraphs = text.trim().split(/\n\s*\n/);
|
|
21
|
+
var summary = heading || plainText(paragraphs[paragraphs.length - 1]);
|
|
22
|
+
if (!heading) {
|
|
23
|
+
var sentence = summary.match(/^.*?[.!?。!?](?:\s|$)/);
|
|
24
|
+
if (sentence) summary = sentence[0].trim();
|
|
25
|
+
}
|
|
26
|
+
if (!summary) return "Thinking";
|
|
27
|
+
return summary.length > 140 ? summary.slice(0, 139).trimEnd() + "…" : summary;
|
|
28
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { thinkingSummary } from './thinking-summary.js';
|
|
2
|
+
import { renderMarkdown } from './markdown.js';
|
|
3
|
+
|
|
4
|
+
export function prepareThinkingView(el) {
|
|
5
|
+
el.hidden = false;
|
|
6
|
+
el.classList.remove("empty", "done", "expanded", "thinking-live");
|
|
7
|
+
var header = el.querySelector(".thinking-header");
|
|
8
|
+
header.setAttribute("role", "button");
|
|
9
|
+
header.setAttribute("aria-expanded", "false");
|
|
10
|
+
header.setAttribute("aria-disabled", "true");
|
|
11
|
+
header.tabIndex = -1;
|
|
12
|
+
header.style.cursor = "";
|
|
13
|
+
el.querySelector(".thinking-chevron").style.display = "";
|
|
14
|
+
el.querySelector(".thinking-label").textContent = "Thinking";
|
|
15
|
+
el.querySelector(".thinking-label").removeAttribute("title");
|
|
16
|
+
el.querySelector(".thinking-duration").textContent = "";
|
|
17
|
+
el.querySelector(".thinking-content").textContent = "";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function bindThinkingView(el) {
|
|
21
|
+
var header = el.querySelector(".thinking-header");
|
|
22
|
+
function toggle() {
|
|
23
|
+
if (header.getAttribute("aria-disabled") === "true") return;
|
|
24
|
+
el.classList.toggle("expanded");
|
|
25
|
+
header.setAttribute("aria-expanded", el.classList.contains("expanded") ? "true" : "false");
|
|
26
|
+
}
|
|
27
|
+
header.addEventListener("click", toggle);
|
|
28
|
+
header.addEventListener("keydown", function (event) {
|
|
29
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
toggle();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function updateThinkingView(el, summaryText, detailsText) {
|
|
36
|
+
var summary = thinkingSummary(summaryText);
|
|
37
|
+
var label = el.querySelector(".thinking-label");
|
|
38
|
+
label.textContent = summary;
|
|
39
|
+
label.title = summary;
|
|
40
|
+
el.querySelector(".thinking-content").innerHTML = detailsText.trim() ? renderMarkdown(detailsText) : "";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function setThinkingViewLive(el, live) {
|
|
44
|
+
el.classList.toggle("thinking-live", !!live);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function beginThinkingView(el, shimmer) {
|
|
48
|
+
el.hidden = false;
|
|
49
|
+
el.classList.remove("done", "empty", "expanded");
|
|
50
|
+
setThinkingViewLive(el, shimmer);
|
|
51
|
+
var header = el.querySelector(".thinking-header");
|
|
52
|
+
header.setAttribute("aria-expanded", "false");
|
|
53
|
+
header.setAttribute("aria-disabled", "true");
|
|
54
|
+
header.tabIndex = -1;
|
|
55
|
+
header.style.cursor = "";
|
|
56
|
+
el.querySelector(".thinking-chevron").style.display = "";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function finishThinkingView(el, summaryText, detailsText) {
|
|
60
|
+
el.classList.remove("thinking-live");
|
|
61
|
+
el.classList.add("done");
|
|
62
|
+
el.classList.remove("expanded");
|
|
63
|
+
updateThinkingView(el, summaryText, detailsText);
|
|
64
|
+
var hasContent = !!detailsText.trim();
|
|
65
|
+
var header = el.querySelector(".thinking-header");
|
|
66
|
+
header.setAttribute("aria-expanded", "false");
|
|
67
|
+
header.setAttribute("aria-disabled", hasContent ? "false" : "true");
|
|
68
|
+
header.tabIndex = hasContent ? 0 : -1;
|
|
69
|
+
header.style.cursor = hasContent ? "pointer" : "default";
|
|
70
|
+
el.hidden = !hasContent;
|
|
71
|
+
if (!hasContent) {
|
|
72
|
+
el.classList.add("empty");
|
|
73
|
+
el.querySelector(".thinking-chevron").style.display = "none";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// tool-palette-order.js — the tool registry and the pure ordering rules.
|
|
2
|
+
//
|
|
3
|
+
// Extracted from tool-palette.js, which owns the DOM. Everything here is
|
|
4
|
+
// pure data and pure functions over id lists: what tools exist, and what a
|
|
5
|
+
// stored preference means today. Keeping it separate keeps the palette module
|
|
6
|
+
// under the size limit and makes the ordering rules directly testable without
|
|
7
|
+
// a browser.
|
|
8
|
+
//
|
|
9
|
+
// Two kinds of rule act on a stored preference, in this order:
|
|
10
|
+
// - legacy remap: an id that was replaced by another tool
|
|
11
|
+
// - retirement: an id whose tool was withdrawn with no replacement
|
|
12
|
+
//
|
|
13
|
+
// Neither rule ever overrides a live choice. A preference naming a tool that
|
|
14
|
+
// still exists is honored exactly as saved; registry order is only the default
|
|
15
|
+
// for a fresh palette and the append order for tools a saved list predates.
|
|
16
|
+
|
|
17
|
+
// Registry order = default order for users who haven't customized. Users
|
|
18
|
+
// with saved preferences keep their own order (applyPreferences uses
|
|
19
|
+
// saved order first, then appends any registry entries the user's saved
|
|
20
|
+
// list doesn't mention). So changes here only affect fresh palettes.
|
|
21
|
+
var SESSION_TOOLS = [
|
|
22
|
+
{ id: "file-browser-btn", icon: "folder-tree", label: "File browser" },
|
|
23
|
+
{ id: "terminal-sidebar-btn", icon: "square-terminal", label: "Terminal", countId: "terminal-sidebar-count" },
|
|
24
|
+
{ id: "sticky-notes-sidebar-btn", icon: "sticky-note", label: "Sticky Notes", countId: "sticky-notes-sidebar-count" },
|
|
25
|
+
{ id: "project-logs-btn", icon: "notebook-tabs", label: "Logs", countId: "project-logs-count" },
|
|
26
|
+
{ id: "loop-tool-btn", icon: "repeat", label: "Loop" },
|
|
27
|
+
{ id: "mcp-btn", icon: "cable", label: "MCP Servers", countId: "mcp-sidebar-count" },
|
|
28
|
+
{ id: "skills-btn", icon: "puzzle", label: "Skills" },
|
|
29
|
+
{ id: "scheduler-btn", icon: "calendar-clock", label: "Scheduled Tasks" },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
var MATE_TOOLS = [
|
|
33
|
+
{ id: "mate-memory-btn", icon: "brain", label: "Memory", countId: "mate-memory-count" },
|
|
34
|
+
{ id: "mate-knowledge-btn", icon: "book-open", label: "Knowledge", countId: "mate-knowledge-count" },
|
|
35
|
+
{ id: "mate-sticky-notes-btn", icon: "sticky-note", label: "Sticky Notes" },
|
|
36
|
+
{ id: "mate-debate-btn", icon: "mic", label: "Debate" },
|
|
37
|
+
{ id: "mate-mcp-btn", icon: "cable", label: "MCP Servers", countId: "mate-mcp-sidebar-count" },
|
|
38
|
+
{ id: "mate-skills-btn", icon: "puzzle", label: "Skills" },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
export var PALETTES = {
|
|
42
|
+
session: {
|
|
43
|
+
tools: SESSION_TOOLS,
|
|
44
|
+
activeContainerId: "session-actions",
|
|
45
|
+
hiddenSectionId: "session-actions-hidden",
|
|
46
|
+
},
|
|
47
|
+
mate: {
|
|
48
|
+
tools: MATE_TOOLS,
|
|
49
|
+
activeContainerId: "mate-sidebar-tools",
|
|
50
|
+
hiddenSectionId: "mate-sidebar-tools-hidden",
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Tools that were replaced in the session palette. A saved preference naming
|
|
55
|
+
// the old id would otherwise silently drop out (its element no longer exists)
|
|
56
|
+
// and the replacement would append to the end of the palette instead of
|
|
57
|
+
// inheriting the slot the user had chosen.
|
|
58
|
+
//
|
|
59
|
+
// There is no active remap. "scheduler-btn" used to map to "project-logs-btn"
|
|
60
|
+
// from when Logs took the Scheduled Tasks slot; that remap is retired because
|
|
61
|
+
// Scheduled Tasks is a live tool again, so a stored "scheduler-btn" once more
|
|
62
|
+
// means exactly what it says and rewriting it would destroy the user's own
|
|
63
|
+
// choice. The mechanism stays as the seam for the next replacement.
|
|
64
|
+
var LEGACY_SESSION_TOOL_IDS = {};
|
|
65
|
+
|
|
66
|
+
// Tools withdrawn from the session palette with no replacement. Git moved to
|
|
67
|
+
// the always-visible repository placard below the tool strip, so it no longer
|
|
68
|
+
// consumes an icon slot. A saved preference naming it is dropped rather than
|
|
69
|
+
// left to linger in stored order forever.
|
|
70
|
+
var RETIRED_SESSION_TOOL_IDS = ["git-sidebar-btn"];
|
|
71
|
+
|
|
72
|
+
function indexOfId(list, id) {
|
|
73
|
+
for (var i = 0; i < list.length; i++) {
|
|
74
|
+
if (list[i] === id) return i;
|
|
75
|
+
}
|
|
76
|
+
return -1;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Rewrite a saved preference so a replaced tool keeps its slot.
|
|
80
|
+
//
|
|
81
|
+
// If the user already has an explicit preference for the replacement, that
|
|
82
|
+
// choice wins and the legacy id is simply dropped: their position, and whether
|
|
83
|
+
// they hid it, are never overridden. Otherwise the legacy id is replaced in
|
|
84
|
+
// place, at the exact index it occupied, in whichever list it appeared in.
|
|
85
|
+
//
|
|
86
|
+
// Exported for tests; returns new arrays and never mutates the input.
|
|
87
|
+
export function normalizeToolPreferences(name, prefs) {
|
|
88
|
+
var order = (prefs && prefs.order) || [];
|
|
89
|
+
var hidden = (prefs && prefs.hidden) || [];
|
|
90
|
+
var result = { order: order.slice(), hidden: hidden.slice(), migrated: false };
|
|
91
|
+
if (name !== "session") return result;
|
|
92
|
+
|
|
93
|
+
var legacyIds = Object.keys(LEGACY_SESSION_TOOL_IDS);
|
|
94
|
+
for (var i = 0; i < legacyIds.length; i++) {
|
|
95
|
+
var legacyId = legacyIds[i];
|
|
96
|
+
var replacementId = LEGACY_SESSION_TOOL_IDS[legacyId];
|
|
97
|
+
var inOrder = indexOfId(result.order, legacyId) !== -1;
|
|
98
|
+
var inHidden = indexOfId(result.hidden, legacyId) !== -1;
|
|
99
|
+
if (!inOrder && !inHidden) continue;
|
|
100
|
+
|
|
101
|
+
var explicit = indexOfId(result.order, replacementId) !== -1
|
|
102
|
+
|| indexOfId(result.hidden, replacementId) !== -1;
|
|
103
|
+
result.order = rewriteList(result.order, legacyId, replacementId, explicit);
|
|
104
|
+
result.hidden = rewriteList(result.hidden, legacyId, replacementId, explicit);
|
|
105
|
+
result.migrated = true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (var r = 0; r < RETIRED_SESSION_TOOL_IDS.length; r++) {
|
|
109
|
+
var retiredId = RETIRED_SESSION_TOOL_IDS[r];
|
|
110
|
+
if (indexOfId(result.order, retiredId) === -1 && indexOfId(result.hidden, retiredId) === -1) continue;
|
|
111
|
+
result.order = dropId(result.order, retiredId);
|
|
112
|
+
result.hidden = dropId(result.hidden, retiredId);
|
|
113
|
+
result.migrated = true;
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function dropId(list, id) {
|
|
119
|
+
var out = [];
|
|
120
|
+
for (var i = 0; i < list.length; i++) {
|
|
121
|
+
if (list[i] !== id) out.push(list[i]);
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Drop the legacy id when the replacement is already chosen explicitly;
|
|
127
|
+
// otherwise swap it in place. Any duplicate of the replacement introduced by
|
|
128
|
+
// the swap is removed, keeping the first occurrence.
|
|
129
|
+
function rewriteList(list, legacyId, replacementId, dropLegacy) {
|
|
130
|
+
var out = [];
|
|
131
|
+
var seen = {};
|
|
132
|
+
for (var i = 0; i < list.length; i++) {
|
|
133
|
+
var id = list[i];
|
|
134
|
+
if (id === legacyId) {
|
|
135
|
+
if (dropLegacy) continue;
|
|
136
|
+
id = replacementId;
|
|
137
|
+
}
|
|
138
|
+
if (seen[id]) continue;
|
|
139
|
+
seen[id] = true;
|
|
140
|
+
out.push(id);
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// tool-palette-overlays.js — transient overlays that sit on top of the palette.
|
|
2
|
+
//
|
|
3
|
+
// Two independent, self-contained pieces extracted from tool-palette.js so
|
|
4
|
+
// that module stays under the size limit: the right-click context menu, and
|
|
5
|
+
// the Cmd/Ctrl+O hotkey pick mode. Neither owns palette state; the menu takes
|
|
6
|
+
// its items (and their actions) from the caller, and pick mode works purely
|
|
7
|
+
// off the rendered tiles. Behavior is unchanged by the move.
|
|
8
|
+
|
|
9
|
+
import { PALETTES } from './tool-palette-order.js';
|
|
10
|
+
|
|
11
|
+
// --- Right-click context menu ---
|
|
12
|
+
|
|
13
|
+
var _openMenu = null;
|
|
14
|
+
|
|
15
|
+
export function openPaletteContextMenu(x, y, items) {
|
|
16
|
+
closePaletteContextMenu();
|
|
17
|
+
var menu = document.createElement('div');
|
|
18
|
+
menu.className = 'tool-palette-ctx-menu';
|
|
19
|
+
for (var i = 0; i < items.length; i++) {
|
|
20
|
+
(function (item) {
|
|
21
|
+
var btn = document.createElement('button');
|
|
22
|
+
btn.type = 'button';
|
|
23
|
+
btn.className = 'tool-palette-ctx-item';
|
|
24
|
+
btn.textContent = item.label;
|
|
25
|
+
btn.addEventListener('click', function (e) {
|
|
26
|
+
e.stopPropagation();
|
|
27
|
+
closePaletteContextMenu();
|
|
28
|
+
item.action();
|
|
29
|
+
});
|
|
30
|
+
menu.appendChild(btn);
|
|
31
|
+
})(items[i]);
|
|
32
|
+
}
|
|
33
|
+
document.body.appendChild(menu);
|
|
34
|
+
// Clamp to viewport.
|
|
35
|
+
var rect = menu.getBoundingClientRect();
|
|
36
|
+
var px = x, py = y;
|
|
37
|
+
if (px + rect.width > window.innerWidth - 4) px = window.innerWidth - rect.width - 4;
|
|
38
|
+
if (py + rect.height > window.innerHeight - 4) py = window.innerHeight - rect.height - 4;
|
|
39
|
+
menu.style.left = px + 'px';
|
|
40
|
+
menu.style.top = py + 'px';
|
|
41
|
+
_openMenu = menu;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function closePaletteContextMenu() {
|
|
45
|
+
if (_openMenu && _openMenu.parentNode) _openMenu.parentNode.removeChild(_openMenu);
|
|
46
|
+
_openMenu = null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
document.addEventListener('click', function () { closePaletteContextMenu(); });
|
|
50
|
+
document.addEventListener('keydown', function (e) {
|
|
51
|
+
if (e.key === 'Escape') closePaletteContextMenu();
|
|
52
|
+
});
|
|
53
|
+
window.addEventListener('blur', function () { closePaletteContextMenu(); });
|
|
54
|
+
window.addEventListener('resize', function () { closePaletteContextMenu(); });
|
|
55
|
+
|
|
56
|
+
// --- Hotkey overlay (Cmd/Ctrl + O) ---
|
|
57
|
+
//
|
|
58
|
+
// Press Cmd/Ctrl + O to reveal a hotkey badge on each visible palette
|
|
59
|
+
// tile. Then press the shown digit (1..9, 0) or letter (a..z) to
|
|
60
|
+
// activate that tool. Escape or clicking away dismisses. Inspired by
|
|
61
|
+
// Vimium / Superhuman-style "pick a tile with one keystroke" flows.
|
|
62
|
+
|
|
63
|
+
var _pickActive = false;
|
|
64
|
+
var HOTKEYS = (function () {
|
|
65
|
+
var k = [];
|
|
66
|
+
for (var i = 1; i <= 9; i++) k.push(String(i));
|
|
67
|
+
k.push('0');
|
|
68
|
+
for (var c = 97; c <= 122; c++) k.push(String.fromCharCode(c));
|
|
69
|
+
return k;
|
|
70
|
+
})();
|
|
71
|
+
|
|
72
|
+
function getVisiblePaletteName() {
|
|
73
|
+
// offsetParent is null when the element (or an ancestor) is hidden.
|
|
74
|
+
var mateActive = document.getElementById('mate-sidebar-tools');
|
|
75
|
+
if (mateActive && mateActive.offsetParent !== null) return 'mate';
|
|
76
|
+
var sessionActive = document.getElementById('session-actions');
|
|
77
|
+
if (sessionActive && sessionActive.offsetParent !== null) return 'session';
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function enterToolPickMode() {
|
|
82
|
+
if (_pickActive) return;
|
|
83
|
+
var name = getVisiblePaletteName();
|
|
84
|
+
if (!name) return;
|
|
85
|
+
var active = document.getElementById(PALETTES[name].activeContainerId);
|
|
86
|
+
if (!active) return;
|
|
87
|
+
var tiles = active.querySelectorAll('[data-tool-id]');
|
|
88
|
+
if (tiles.length === 0) return;
|
|
89
|
+
for (var i = 0; i < tiles.length && i < HOTKEYS.length; i++) {
|
|
90
|
+
var key = HOTKEYS[i];
|
|
91
|
+
tiles[i].dataset.hotkey = key;
|
|
92
|
+
var badge = document.createElement('span');
|
|
93
|
+
badge.className = 'tool-palette-hotkey-badge';
|
|
94
|
+
badge.textContent = key.toUpperCase();
|
|
95
|
+
tiles[i].appendChild(badge);
|
|
96
|
+
}
|
|
97
|
+
_pickActive = true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function exitToolPickMode() {
|
|
101
|
+
if (!_pickActive) return;
|
|
102
|
+
_pickActive = false;
|
|
103
|
+
var badges = document.querySelectorAll('.tool-palette-hotkey-badge');
|
|
104
|
+
for (var i = 0; i < badges.length; i++) {
|
|
105
|
+
if (badges[i].parentNode) badges[i].parentNode.removeChild(badges[i]);
|
|
106
|
+
}
|
|
107
|
+
var marked = document.querySelectorAll('[data-hotkey]');
|
|
108
|
+
for (var j = 0; j < marked.length; j++) {
|
|
109
|
+
delete marked[j].dataset.hotkey;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Capture phase so we reliably preempt typing and browser shortcuts
|
|
114
|
+
// when focus is in an input.
|
|
115
|
+
document.addEventListener('keydown', function (e) {
|
|
116
|
+
// Pick mode: consume the next printable key.
|
|
117
|
+
if (_pickActive) {
|
|
118
|
+
if (e.key === 'Escape') {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
exitToolPickMode();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// Ignore modifier-only keys; react to single-character keys.
|
|
124
|
+
if (e.key.length !== 1) return;
|
|
125
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
126
|
+
var key = e.key.toLowerCase();
|
|
127
|
+
var tile = document.querySelector('[data-hotkey="' + key + '"]');
|
|
128
|
+
if (tile) {
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
exitToolPickMode();
|
|
131
|
+
tile.click();
|
|
132
|
+
} else {
|
|
133
|
+
// Unknown key exits pick mode without triggering a tile.
|
|
134
|
+
exitToolPickMode();
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Enter pick mode on Cmd/Ctrl + O (letter O). Intercepted globally
|
|
140
|
+
// including inside text inputs — the browser's native Cmd+O is a
|
|
141
|
+
// file-picker dialog Clay never wants, so taking it over costs the
|
|
142
|
+
// user nothing and is especially useful when jumping to a tool from
|
|
143
|
+
// the session search box.
|
|
144
|
+
if ((e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey && e.key && e.key.toLowerCase() === 'o') {
|
|
145
|
+
if (!getVisiblePaletteName()) return;
|
|
146
|
+
e.preventDefault();
|
|
147
|
+
enterToolPickMode();
|
|
148
|
+
}
|
|
149
|
+
}, true);
|
|
150
|
+
|
|
151
|
+
// Dismiss the overlay on click-away, blur, or viewport changes so it
|
|
152
|
+
// never lingers invisibly.
|
|
153
|
+
document.addEventListener('click', function () { if (_pickActive) exitToolPickMode(); });
|
|
154
|
+
window.addEventListener('blur', function () { if (_pickActive) exitToolPickMode(); });
|
|
155
|
+
window.addEventListener('resize', function () { if (_pickActive) exitToolPickMode(); });
|
|
156
|
+
|
|
157
|
+
export function isToolPickActive() {
|
|
158
|
+
return _pickActive;
|
|
159
|
+
}
|