clay-server 4.0.0-beta.8 → 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.
Files changed (185) hide show
  1. package/lib/capsule-display-floor.js +51 -0
  2. package/lib/capsule-frame-server.js +255 -0
  3. package/lib/capsule-mate-turn.js +105 -0
  4. package/lib/capsule-pig-logic.js +300 -0
  5. package/lib/capsule-server-runtimes.js +39 -0
  6. package/lib/capsules/pig/display.js +198 -0
  7. package/lib/capsules/pig/manifest.json +9 -0
  8. package/lib/capsules/pig/ui.json +84 -0
  9. package/lib/config.js +25 -1
  10. package/lib/daemon-projects.js +13 -8
  11. package/lib/daemon.js +127 -14
  12. package/lib/git-cli.js +95 -1
  13. package/lib/knowledge-import.js +436 -0
  14. package/lib/knowledge-record-store.js +188 -0
  15. package/lib/knowledge-search.js +240 -0
  16. package/lib/mate-knowledge-mcp-server.js +151 -0
  17. package/lib/mate-knowledge-migration.js +283 -0
  18. package/lib/mate-knowledge-service.js +488 -0
  19. package/lib/mate-knowledge-sync.js +482 -0
  20. package/lib/migrate-single-user.js +2 -0
  21. package/lib/notes-lifecycle.js +149 -0
  22. package/lib/notes.js +60 -3
  23. package/lib/os-users.js +3 -1
  24. package/lib/project-capsule-catalog.js +9 -1
  25. package/lib/project-capsule-turn.js +96 -0
  26. package/lib/project-connection.js +15 -1
  27. package/lib/project-file-history.js +223 -0
  28. package/lib/project-file-watch.js +77 -49
  29. package/lib/project-filesystem.js +31 -220
  30. package/lib/project-http.js +15 -1
  31. package/lib/project-knowledge.js +22 -0
  32. package/lib/project-log-context.js +172 -0
  33. package/lib/project-log-feedback-delivery.js +118 -0
  34. package/lib/project-logs-comments.js +137 -0
  35. package/lib/project-logs-context-state.js +42 -0
  36. package/lib/project-logs-mcp-server.js +328 -0
  37. package/lib/project-logs-query.js +185 -0
  38. package/lib/project-logs-root.js +75 -0
  39. package/lib/project-logs-schema.js +259 -0
  40. package/lib/project-logs-service.js +463 -0
  41. package/lib/project-logs-snapshot.js +205 -0
  42. package/lib/project-logs-store.js +495 -0
  43. package/lib/project-logs-versioning.js +132 -0
  44. package/lib/project-logs.js +400 -0
  45. package/lib/project-mate-interaction.js +14 -0
  46. package/lib/project-mate-knowledge.js +110 -0
  47. package/lib/project-memory.js +22 -0
  48. package/lib/project-message-delivery.js +70 -0
  49. package/lib/project-pair-lifecycle.js +460 -0
  50. package/lib/project-request-access.js +66 -0
  51. package/lib/project-session-handoff.js +1 -1
  52. package/lib/project-session-notes.js +64 -13
  53. package/lib/project-session-pair.js +159 -146
  54. package/lib/project-session-spawn.js +1 -1
  55. package/lib/project-sessions.js +58 -13
  56. package/lib/project-user-message.js +53 -4
  57. package/lib/project-worker-permission.js +430 -0
  58. package/lib/project-worker-proposal.js +208 -62
  59. package/lib/project.js +185 -6
  60. package/lib/public/app.js +63 -35
  61. package/lib/public/css/admin.css +2 -2
  62. package/lib/public/css/capsule-ui.css +38 -0
  63. package/lib/public/css/command-palette.css +19 -9
  64. package/lib/public/css/git-placard.css +154 -0
  65. package/lib/public/css/home-hub.css +4 -0
  66. package/lib/public/css/home-session-actions.css +66 -0
  67. package/lib/public/css/home-sidebar.css +66 -0
  68. package/lib/public/css/input.css +84 -3
  69. package/lib/public/css/messages.css +40 -1
  70. package/lib/public/css/mobile-nav.css +80 -0
  71. package/lib/public/css/notifications-center.css +67 -0
  72. package/lib/public/css/pane.css +97 -10
  73. package/lib/public/css/project-logs.css +198 -0
  74. package/lib/public/css/scheduler.css +1 -1
  75. package/lib/public/css/session-actions.css +16 -101
  76. package/lib/public/css/sidebar.css +103 -0
  77. package/lib/public/css/sticky-notes.css +237 -229
  78. package/lib/public/css/worker-proposal.css +36 -3
  79. package/lib/public/index.html +43 -11
  80. package/lib/public/modules/admin.js +8 -8
  81. package/lib/public/modules/app-connection.js +124 -6
  82. package/lib/public/modules/app-home-hub.js +12 -8
  83. package/lib/public/modules/app-message-cards.js +2 -1
  84. package/lib/public/modules/app-messages.js +92 -11
  85. package/lib/public/modules/app-misc.js +2 -14
  86. package/lib/public/modules/app-notifications.js +11 -7
  87. package/lib/public/modules/app-panels.js +3 -2
  88. package/lib/public/modules/app-projects.js +56 -18
  89. package/lib/public/modules/app-rendering.js +1 -0
  90. package/lib/public/modules/capsule-preference.js +44 -0
  91. package/lib/public/modules/chat-bubble-renderer.js +1 -1
  92. package/lib/public/modules/clay-log-links.js +70 -0
  93. package/lib/public/modules/command-palette.js +52 -15
  94. package/lib/public/modules/context-sources.js +0 -3
  95. package/lib/public/modules/git-agent-sessions.js +85 -0
  96. package/lib/public/modules/git-panel.js +111 -84
  97. package/lib/public/modules/git-placard.js +161 -0
  98. package/lib/public/modules/home-chat-empty-state.js +39 -0
  99. package/lib/public/modules/home-chat-identity.js +9 -0
  100. package/lib/public/modules/home-conversations-sheet.js +102 -48
  101. package/lib/public/modules/home-mate-chat.js +15 -35
  102. package/lib/public/modules/home-session-actions.js +6 -0
  103. package/lib/public/modules/home-sidebar-chat-list.js +101 -41
  104. package/lib/public/modules/home-sidebar.js +7 -0
  105. package/lib/public/modules/home-surface-boot.js +7 -3
  106. package/lib/public/modules/home-tool-frame.js +182 -0
  107. package/lib/public/modules/home-tools.js +138 -1
  108. package/lib/public/modules/input.js +38 -7
  109. package/lib/public/modules/markdown.js +2 -0
  110. package/lib/public/modules/mate-sidebar.js +0 -8
  111. package/lib/public/modules/message-delivery.js +97 -0
  112. package/lib/public/modules/paste-modal.js +84 -0
  113. package/lib/public/modules/project-activation.js +45 -0
  114. package/lib/public/modules/project-logs-render.js +405 -0
  115. package/lib/public/modules/project-logs.js +461 -0
  116. package/lib/public/modules/project-removal-target.js +26 -0
  117. package/lib/public/modules/scheduler.js +17 -3
  118. package/lib/public/modules/session-actions.js +29 -105
  119. package/lib/public/modules/session-hierarchy.js +54 -0
  120. package/lib/public/modules/sidebar-mobile.js +40 -15
  121. package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
  122. package/lib/public/modules/sidebar-sessions.js +77 -11
  123. package/lib/public/modules/sidebar.js +17 -5
  124. package/lib/public/modules/split-group-helpers.js +8 -0
  125. package/lib/public/modules/split-pair-ui.js +15 -4
  126. package/lib/public/modules/split-view.js +5 -2
  127. package/lib/public/modules/split-worker-runtime.js +24 -0
  128. package/lib/public/modules/sticky-note-markdown.js +5 -0
  129. package/lib/public/modules/sticky-notes-browser.js +326 -0
  130. package/lib/public/modules/sticky-notes-card.js +360 -0
  131. package/lib/public/modules/sticky-notes-editor.js +291 -0
  132. package/lib/public/modules/sticky-notes-shared.js +90 -0
  133. package/lib/public/modules/sticky-notes.js +170 -946
  134. package/lib/public/modules/thinking-lifecycle.js +175 -0
  135. package/lib/public/modules/thinking-summary.js +28 -0
  136. package/lib/public/modules/thinking-view.js +75 -0
  137. package/lib/public/modules/tool-palette-order.js +143 -0
  138. package/lib/public/modules/tool-palette-overlays.js +159 -0
  139. package/lib/public/modules/tool-palette.js +21 -188
  140. package/lib/public/modules/tools.js +31 -123
  141. package/lib/public/modules/update-snooze.js +169 -0
  142. package/lib/public/modules/user-settings.js +4 -0
  143. package/lib/public/modules/worker-pane-lock.js +176 -0
  144. package/lib/public/modules/worker-proposal-state.js +16 -0
  145. package/lib/public/modules/worker-proposal.js +49 -20
  146. package/lib/public/style.css +3 -1
  147. package/lib/sdk-bridge.js +135 -46
  148. package/lib/sdk-message-processor.js +36 -19
  149. package/lib/server-experimental-settings.js +49 -0
  150. package/lib/server-global-ws.js +11 -0
  151. package/lib/server-home-chat-events.js +31 -1
  152. package/lib/server-home-chat.js +27 -4
  153. package/lib/server-home-clay-entry.js +1 -1
  154. package/lib/server-home-clay-session-links.js +34 -5
  155. package/lib/server-settings.js +5 -0
  156. package/lib/server-tools.js +127 -9
  157. package/lib/server.js +137 -22
  158. package/lib/session-driver-eligibility.js +38 -0
  159. package/lib/session-driver-orchestration.js +31 -0
  160. package/lib/session-hygiene.js +3 -0
  161. package/lib/session-notes-mcp-server.js +25 -5
  162. package/lib/session-pair-factory.js +247 -0
  163. package/lib/session-pair-mcp-server.js +46 -8
  164. package/lib/session-pair-prompts.js +79 -0
  165. package/lib/session-pair-turn-control.js +153 -0
  166. package/lib/session-provenance.js +119 -0
  167. package/lib/session-spawn-mcp-server.js +1 -1
  168. package/lib/session-split-groups.js +4 -1
  169. package/lib/session-title-policy.js +60 -0
  170. package/lib/session-visibility.js +41 -0
  171. package/lib/sessions.js +57 -10
  172. package/lib/tools-registry.js +89 -10
  173. package/lib/update-snooze.js +437 -0
  174. package/lib/users-experimental-preferences.js +30 -0
  175. package/lib/users-permissions.js +6 -6
  176. package/lib/users.js +22 -16
  177. package/lib/workspace-query-access.js +102 -0
  178. package/lib/workspace-query-service.js +24 -17
  179. package/lib/worktree.js +25 -38
  180. package/lib/ws-schema.js +38 -7
  181. package/lib/yoke/adapters/claude-worker.js +16 -6
  182. package/lib/yoke/adapters/claude.js +12 -0
  183. package/lib/yoke/adapters/codex.js +24 -14
  184. package/lib/yoke/vendor-registry.js +1 -1
  185. 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
+ }