clay-server 4.0.0-beta.2 → 4.0.0-beta.21

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 (177) hide show
  1. package/lib/background-task-timing.js +105 -0
  2. package/lib/capsule-display-floor.js +51 -0
  3. package/lib/capsule-frame-server.js +255 -0
  4. package/lib/capsule-mate-turn.js +105 -0
  5. package/lib/capsule-pig-logic.js +300 -0
  6. package/lib/capsule-server-runtimes.js +39 -0
  7. package/lib/capsules/pig/display.js +198 -0
  8. package/lib/capsules/pig/manifest.json +9 -0
  9. package/lib/capsules/pig/ui.json +84 -0
  10. package/lib/config.js +25 -1
  11. package/lib/daemon-projects.js +13 -8
  12. package/lib/daemon.js +127 -14
  13. package/lib/git-cli.js +95 -1
  14. package/lib/knowledge-import.js +436 -0
  15. package/lib/knowledge-record-store.js +188 -0
  16. package/lib/knowledge-search.js +240 -0
  17. package/lib/mate-knowledge-mcp-server.js +151 -0
  18. package/lib/mate-knowledge-migration.js +283 -0
  19. package/lib/mate-knowledge-service.js +488 -0
  20. package/lib/mate-knowledge-sync.js +482 -0
  21. package/lib/migrate-single-user.js +2 -0
  22. package/lib/notes-lifecycle.js +149 -0
  23. package/lib/notes.js +60 -3
  24. package/lib/os-users.js +3 -1
  25. package/lib/project-capsule-catalog.js +9 -1
  26. package/lib/project-capsule-turn.js +96 -0
  27. package/lib/project-connection.js +14 -1
  28. package/lib/project-http.js +15 -1
  29. package/lib/project-knowledge.js +22 -0
  30. package/lib/project-log-context.js +172 -0
  31. package/lib/project-log-feedback-delivery.js +118 -0
  32. package/lib/project-logs-comments.js +137 -0
  33. package/lib/project-logs-context-state.js +42 -0
  34. package/lib/project-logs-mcp-server.js +328 -0
  35. package/lib/project-logs-query.js +185 -0
  36. package/lib/project-logs-root.js +75 -0
  37. package/lib/project-logs-schema.js +259 -0
  38. package/lib/project-logs-service.js +463 -0
  39. package/lib/project-logs-snapshot.js +205 -0
  40. package/lib/project-logs-store.js +495 -0
  41. package/lib/project-logs-versioning.js +132 -0
  42. package/lib/project-logs.js +400 -0
  43. package/lib/project-mate-interaction.js +14 -0
  44. package/lib/project-mate-knowledge.js +110 -0
  45. package/lib/project-memory.js +22 -0
  46. package/lib/project-message-delivery.js +70 -0
  47. package/lib/project-pair-lifecycle.js +460 -0
  48. package/lib/project-session-handoff.js +1 -1
  49. package/lib/project-session-notes.js +64 -13
  50. package/lib/project-session-pair.js +159 -146
  51. package/lib/project-session-spawn.js +1 -1
  52. package/lib/project-sessions.js +58 -13
  53. package/lib/project-user-message.js +53 -4
  54. package/lib/project-vendor-login.js +20 -3
  55. package/lib/project-worker-permission.js +430 -0
  56. package/lib/project-worker-proposal.js +208 -62
  57. package/lib/project.js +178 -6
  58. package/lib/public/app.js +58 -35
  59. package/lib/public/css/admin.css +2 -2
  60. package/lib/public/css/capsule-ui.css +38 -0
  61. package/lib/public/css/command-palette.css +19 -9
  62. package/lib/public/css/git-placard.css +154 -0
  63. package/lib/public/css/home-hub.css +4 -0
  64. package/lib/public/css/home-session-actions.css +66 -0
  65. package/lib/public/css/home-sidebar.css +66 -0
  66. package/lib/public/css/input.css +163 -28
  67. package/lib/public/css/mobile-nav.css +80 -0
  68. package/lib/public/css/notifications-center.css +67 -0
  69. package/lib/public/css/pane.css +52 -0
  70. package/lib/public/css/project-logs.css +198 -0
  71. package/lib/public/css/scheduler.css +1 -1
  72. package/lib/public/css/session-actions.css +16 -101
  73. package/lib/public/css/sidebar.css +103 -0
  74. package/lib/public/css/sticky-notes.css +237 -229
  75. package/lib/public/css/worker-proposal.css +34 -1
  76. package/lib/public/index.html +45 -11
  77. package/lib/public/modules/admin.js +8 -8
  78. package/lib/public/modules/app-connection.js +109 -4
  79. package/lib/public/modules/app-home-hub.js +19 -7
  80. package/lib/public/modules/app-message-cards.js +2 -1
  81. package/lib/public/modules/app-messages.js +84 -7
  82. package/lib/public/modules/app-misc.js +2 -14
  83. package/lib/public/modules/app-notifications.js +11 -7
  84. package/lib/public/modules/app-panels.js +46 -4
  85. package/lib/public/modules/app-projects.js +13 -8
  86. package/lib/public/modules/app-rendering.js +1 -0
  87. package/lib/public/modules/background-tasks-ui.js +139 -16
  88. package/lib/public/modules/capsule-preference.js +44 -0
  89. package/lib/public/modules/chat-bubble-renderer.js +1 -1
  90. package/lib/public/modules/clay-log-links.js +70 -0
  91. package/lib/public/modules/command-palette.js +52 -15
  92. package/lib/public/modules/context-sources.js +0 -3
  93. package/lib/public/modules/git-agent-sessions.js +85 -0
  94. package/lib/public/modules/git-panel.js +111 -84
  95. package/lib/public/modules/git-placard.js +161 -0
  96. package/lib/public/modules/home-chat-empty-state.js +39 -0
  97. package/lib/public/modules/home-chat-identity.js +9 -0
  98. package/lib/public/modules/home-conversations-sheet.js +102 -48
  99. package/lib/public/modules/home-mate-chat.js +15 -35
  100. package/lib/public/modules/home-session-actions.js +6 -0
  101. package/lib/public/modules/home-sidebar-chat-list.js +101 -41
  102. package/lib/public/modules/home-sidebar.js +7 -0
  103. package/lib/public/modules/home-tool-frame.js +182 -0
  104. package/lib/public/modules/home-tools.js +138 -1
  105. package/lib/public/modules/input.js +38 -7
  106. package/lib/public/modules/markdown.js +2 -0
  107. package/lib/public/modules/mate-sidebar.js +0 -8
  108. package/lib/public/modules/message-delivery.js +97 -0
  109. package/lib/public/modules/paste-modal.js +84 -0
  110. package/lib/public/modules/project-logs-render.js +405 -0
  111. package/lib/public/modules/project-logs.js +461 -0
  112. package/lib/public/modules/project-removal-target.js +26 -0
  113. package/lib/public/modules/scheduler.js +17 -3
  114. package/lib/public/modules/session-actions.js +29 -105
  115. package/lib/public/modules/session-hierarchy.js +54 -0
  116. package/lib/public/modules/sidebar-mobile.js +40 -15
  117. package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
  118. package/lib/public/modules/sidebar-sessions.js +77 -11
  119. package/lib/public/modules/sidebar.js +17 -5
  120. package/lib/public/modules/split-group-helpers.js +8 -0
  121. package/lib/public/modules/split-view.js +5 -2
  122. package/lib/public/modules/sticky-note-markdown.js +5 -0
  123. package/lib/public/modules/sticky-notes-browser.js +326 -0
  124. package/lib/public/modules/sticky-notes-card.js +360 -0
  125. package/lib/public/modules/sticky-notes-editor.js +291 -0
  126. package/lib/public/modules/sticky-notes-shared.js +90 -0
  127. package/lib/public/modules/sticky-notes.js +170 -946
  128. package/lib/public/modules/tool-palette-order.js +143 -0
  129. package/lib/public/modules/tool-palette-overlays.js +159 -0
  130. package/lib/public/modules/tool-palette.js +21 -188
  131. package/lib/public/modules/tools.js +21 -1
  132. package/lib/public/modules/update-snooze.js +169 -0
  133. package/lib/public/modules/user-settings.js +4 -0
  134. package/lib/public/modules/worker-pane-lock.js +176 -0
  135. package/lib/public/modules/worker-proposal-state.js +16 -0
  136. package/lib/public/modules/worker-proposal.js +49 -12
  137. package/lib/public/style.css +3 -1
  138. package/lib/sdk-bridge.js +132 -46
  139. package/lib/sdk-message-processor.js +60 -14
  140. package/lib/server-experimental-settings.js +49 -0
  141. package/lib/server-global-ws.js +11 -0
  142. package/lib/server-home-chat-events.js +31 -1
  143. package/lib/server-home-chat.js +27 -4
  144. package/lib/server-home-clay-entry.js +1 -1
  145. package/lib/server-home-clay-session-links.js +34 -5
  146. package/lib/server-settings.js +5 -0
  147. package/lib/server-tools.js +127 -9
  148. package/lib/server.js +124 -18
  149. package/lib/session-driver-eligibility.js +38 -0
  150. package/lib/session-driver-orchestration.js +31 -0
  151. package/lib/session-hygiene.js +3 -0
  152. package/lib/session-notes-mcp-server.js +25 -5
  153. package/lib/session-pair-factory.js +247 -0
  154. package/lib/session-pair-mcp-server.js +46 -8
  155. package/lib/session-pair-prompts.js +79 -0
  156. package/lib/session-pair-turn-control.js +153 -0
  157. package/lib/session-provenance.js +119 -0
  158. package/lib/session-spawn-mcp-server.js +1 -1
  159. package/lib/session-split-groups.js +4 -1
  160. package/lib/session-title-policy.js +60 -0
  161. package/lib/session-visibility.js +39 -0
  162. package/lib/sessions.js +61 -11
  163. package/lib/tools-registry.js +89 -10
  164. package/lib/update-snooze.js +437 -0
  165. package/lib/users-experimental-preferences.js +30 -0
  166. package/lib/users-permissions.js +2 -2
  167. package/lib/users.js +22 -16
  168. package/lib/workspace-query-access.js +102 -0
  169. package/lib/workspace-query-service.js +24 -17
  170. package/lib/worktree.js +25 -38
  171. package/lib/ws-schema.js +38 -7
  172. package/lib/yoke/adapters/claude-worker.js +16 -6
  173. package/lib/yoke/adapters/claude.js +81 -4
  174. package/lib/yoke/adapters/codex.js +63 -15
  175. package/lib/yoke/codex-background-tasks.js +8 -2
  176. package/lib/yoke/vendor-registry.js +1 -1
  177. package/package.json +3 -3
@@ -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
+ }
@@ -14,44 +14,13 @@
14
14
  // An in-flight save is debounced so rapid drag reorders don't spam.
15
15
 
16
16
  import { refreshIcons } from './icons.js';
17
-
18
- // Registry order = default order for users who haven't customized. Users
19
- // with saved preferences keep their own order (applyPreferences uses
20
- // saved order first, then appends any registry entries the user's saved
21
- // list doesn't mention). So changes here only affect fresh palettes.
22
- var SESSION_TOOLS = [
23
- { id: "file-browser-btn", icon: "folder-tree", label: "File browser" },
24
- { id: "terminal-sidebar-btn", icon: "square-terminal", label: "Terminal", countId: "terminal-sidebar-count" },
25
- { id: "sticky-notes-sidebar-btn", icon: "sticky-note", label: "Sticky Notes", countId: "sticky-notes-sidebar-count" },
26
- { id: "scheduler-btn", icon: "calendar-clock", label: "Scheduled Tasks" },
27
- { id: "loop-tool-btn", icon: "repeat", label: "Loop" },
28
- { id: "git-sidebar-btn", icon: "git-branch", label: "Git", countId: "git-sidebar-count" },
29
- { id: "mcp-btn", icon: "cable", label: "MCP Servers", countId: "mcp-sidebar-count" },
30
- { id: "skills-btn", icon: "puzzle", label: "Skills" },
31
- ];
32
-
33
- var MATE_TOOLS = [
34
- { id: "mate-memory-btn", icon: "brain", label: "Memory", countId: "mate-memory-count" },
35
- { id: "mate-knowledge-btn", icon: "book-open", label: "Knowledge", countId: "mate-knowledge-count" },
36
- { id: "mate-sticky-notes-btn", icon: "sticky-note", label: "Sticky Notes" },
37
- { id: "mate-scheduler-btn", icon: "calendar-clock", label: "Scheduled Tasks" },
38
- { id: "mate-debate-btn", icon: "mic", label: "Debate" },
39
- { id: "mate-mcp-btn", icon: "cable", label: "MCP Servers", countId: "mate-mcp-sidebar-count" },
40
- { id: "mate-skills-btn", icon: "puzzle", label: "Skills" },
41
- ];
42
-
43
- var PALETTES = {
44
- session: {
45
- tools: SESSION_TOOLS,
46
- activeContainerId: "session-actions",
47
- hiddenSectionId: "session-actions-hidden",
48
- },
49
- mate: {
50
- tools: MATE_TOOLS,
51
- activeContainerId: "mate-sidebar-tools",
52
- hiddenSectionId: "mate-sidebar-tools-hidden",
53
- },
54
- };
17
+ import { PALETTES, normalizeToolPreferences } from './tool-palette-order.js';
18
+ import {
19
+ enterToolPickMode,
20
+ exitToolPickMode,
21
+ isToolPickActive,
22
+ openPaletteContextMenu
23
+ } from './tool-palette-overlays.js';
55
24
 
56
25
  var _saveTimers = {};
57
26
  var _draggingEl = null;
@@ -86,7 +55,7 @@ export function initToolPalettes() {
86
55
  // handler below, which would immediately tear down the pick-mode
87
56
  // we just entered.
88
57
  e.stopPropagation();
89
- if (_pickActive) exitToolPickMode();
58
+ if (isToolPickActive()) exitToolPickMode();
90
59
  else enterToolPickMode();
91
60
  });
92
61
  }
@@ -303,152 +272,6 @@ function moveToActive(name, toolId) {
303
272
  // attached in buildToolButton before any external module attaches
304
273
  // handlers by ID. See the stopImmediatePropagation logic there.)
305
274
 
306
- // --- Right-click context menu ---
307
-
308
- var _openMenu = null;
309
-
310
- function openPaletteContextMenu(x, y, items) {
311
- closePaletteContextMenu();
312
- var menu = document.createElement('div');
313
- menu.className = 'tool-palette-ctx-menu';
314
- for (var i = 0; i < items.length; i++) {
315
- (function (item) {
316
- var btn = document.createElement('button');
317
- btn.type = 'button';
318
- btn.className = 'tool-palette-ctx-item';
319
- btn.textContent = item.label;
320
- btn.addEventListener('click', function (e) {
321
- e.stopPropagation();
322
- closePaletteContextMenu();
323
- item.action();
324
- });
325
- menu.appendChild(btn);
326
- })(items[i]);
327
- }
328
- document.body.appendChild(menu);
329
- // Clamp to viewport.
330
- var rect = menu.getBoundingClientRect();
331
- var px = x, py = y;
332
- if (px + rect.width > window.innerWidth - 4) px = window.innerWidth - rect.width - 4;
333
- if (py + rect.height > window.innerHeight - 4) py = window.innerHeight - rect.height - 4;
334
- menu.style.left = px + 'px';
335
- menu.style.top = py + 'px';
336
- _openMenu = menu;
337
- }
338
-
339
- function closePaletteContextMenu() {
340
- if (_openMenu && _openMenu.parentNode) _openMenu.parentNode.removeChild(_openMenu);
341
- _openMenu = null;
342
- }
343
-
344
- document.addEventListener('click', function () { closePaletteContextMenu(); });
345
- document.addEventListener('keydown', function (e) {
346
- if (e.key === 'Escape') closePaletteContextMenu();
347
- });
348
- window.addEventListener('blur', function () { closePaletteContextMenu(); });
349
- window.addEventListener('resize', function () { closePaletteContextMenu(); });
350
-
351
- // --- Hotkey overlay (Cmd/Ctrl + O) ---
352
- //
353
- // Press Cmd/Ctrl + O to reveal a hotkey badge on each visible palette
354
- // tile. Then press the shown digit (1..9, 0) or letter (a..z) to
355
- // activate that tool. Escape or clicking away dismisses. Inspired by
356
- // Vimium / Superhuman-style "pick a tile with one keystroke" flows.
357
-
358
- var _pickActive = false;
359
- var HOTKEYS = (function () {
360
- var k = [];
361
- for (var i = 1; i <= 9; i++) k.push(String(i));
362
- k.push('0');
363
- for (var c = 97; c <= 122; c++) k.push(String.fromCharCode(c));
364
- return k;
365
- })();
366
-
367
- function getVisiblePaletteName() {
368
- // offsetParent is null when the element (or an ancestor) is hidden.
369
- var mateActive = document.getElementById('mate-sidebar-tools');
370
- if (mateActive && mateActive.offsetParent !== null) return 'mate';
371
- var sessionActive = document.getElementById('session-actions');
372
- if (sessionActive && sessionActive.offsetParent !== null) return 'session';
373
- return null;
374
- }
375
-
376
- function enterToolPickMode() {
377
- if (_pickActive) return;
378
- var name = getVisiblePaletteName();
379
- if (!name) return;
380
- var active = document.getElementById(PALETTES[name].activeContainerId);
381
- if (!active) return;
382
- var tiles = active.querySelectorAll('[data-tool-id]');
383
- if (tiles.length === 0) return;
384
- for (var i = 0; i < tiles.length && i < HOTKEYS.length; i++) {
385
- var key = HOTKEYS[i];
386
- tiles[i].dataset.hotkey = key;
387
- var badge = document.createElement('span');
388
- badge.className = 'tool-palette-hotkey-badge';
389
- badge.textContent = key.toUpperCase();
390
- tiles[i].appendChild(badge);
391
- }
392
- _pickActive = true;
393
- }
394
-
395
- function exitToolPickMode() {
396
- if (!_pickActive) return;
397
- _pickActive = false;
398
- var badges = document.querySelectorAll('.tool-palette-hotkey-badge');
399
- for (var i = 0; i < badges.length; i++) {
400
- if (badges[i].parentNode) badges[i].parentNode.removeChild(badges[i]);
401
- }
402
- var marked = document.querySelectorAll('[data-hotkey]');
403
- for (var j = 0; j < marked.length; j++) {
404
- delete marked[j].dataset.hotkey;
405
- }
406
- }
407
-
408
- // Capture phase so we reliably preempt typing and browser shortcuts
409
- // when focus is in an input.
410
- document.addEventListener('keydown', function (e) {
411
- // Pick mode: consume the next printable key.
412
- if (_pickActive) {
413
- if (e.key === 'Escape') {
414
- e.preventDefault();
415
- exitToolPickMode();
416
- return;
417
- }
418
- // Ignore modifier-only keys; react to single-character keys.
419
- if (e.key.length !== 1) return;
420
- if (e.ctrlKey || e.metaKey || e.altKey) return;
421
- var key = e.key.toLowerCase();
422
- var tile = document.querySelector('[data-hotkey="' + key + '"]');
423
- if (tile) {
424
- e.preventDefault();
425
- exitToolPickMode();
426
- tile.click();
427
- } else {
428
- // Unknown key exits pick mode without triggering a tile.
429
- exitToolPickMode();
430
- }
431
- return;
432
- }
433
-
434
- // Enter pick mode on Cmd/Ctrl + O (letter O). Intercepted globally
435
- // including inside text inputs — the browser's native Cmd+O is a
436
- // file-picker dialog Clay never wants, so taking it over costs the
437
- // user nothing and is especially useful when jumping to a tool from
438
- // the session search box.
439
- if ((e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey && e.key && e.key.toLowerCase() === 'o') {
440
- if (!getVisiblePaletteName()) return;
441
- e.preventDefault();
442
- enterToolPickMode();
443
- }
444
- }, true);
445
-
446
- // Dismiss the overlay on click-away, blur, or viewport changes so it
447
- // never lingers invisibly.
448
- document.addEventListener('click', function () { if (_pickActive) exitToolPickMode(); });
449
- window.addEventListener('blur', function () { if (_pickActive) exitToolPickMode(); });
450
- window.addEventListener('resize', function () { if (_pickActive) exitToolPickMode(); });
451
-
452
275
  function getDragAfterElement(container, x, y) {
453
276
  var tiles = Array.prototype.slice.call(
454
277
  container.querySelectorAll('[data-tool-id]:not(.dragging)')
@@ -495,13 +318,17 @@ function applyPreferences(name, prefs) {
495
318
  var hiddenGrid = hiddenSection ? hiddenSection.querySelector('.tool-palette-hidden-grid') : null;
496
319
  if (!active || !hiddenGrid) return;
497
320
 
321
+ var normalized = normalizeToolPreferences(name, prefs);
322
+
498
323
  var hiddenSet = {};
499
- var hiddenList = prefs.hidden || [];
324
+ var hiddenList = normalized.hidden;
500
325
  for (var i = 0; i < hiddenList.length; i++) hiddenSet[hiddenList[i]] = true;
501
326
 
502
- var orderList = prefs.order || [];
327
+ var orderList = normalized.order;
503
328
  // Any tools not in the stored order are appended in registry order so
504
- // newly-added tools surface automatically for existing users.
329
+ // newly-added tools surface automatically for existing users. This is how
330
+ // a restored tool reaches a saved palette that predates it: no rewrite, no
331
+ // server state, just the normal new-registry append.
505
332
  var placed = {};
506
333
  for (var j = 0; j < orderList.length; j++) {
507
334
  var id = orderList[j];
@@ -525,6 +352,12 @@ function applyPreferences(name, prefs) {
525
352
  hiddenGrid.appendChild(hbtn);
526
353
  }
527
354
  }
355
+
356
+ // Write the rewritten preference back once, through the normal save path, so
357
+ // the migration survives reloads and reaches the user's other devices. The
358
+ // save reads the DOM this function just arranged, so it records exactly what
359
+ // is on screen.
360
+ if (normalized.migrated) queueSave(name);
528
361
  }
529
362
 
530
363
  function queueSave(name) {
@@ -203,6 +203,23 @@ export function toolActivityText(name, input) {
203
203
  return "Running " + name + "...";
204
204
  }
205
205
 
206
+ // The sublabel for a call that has finished. `toolActivityText` is written in
207
+ // the present progressive for a call in flight, so re-asserting it after a
208
+ // result has arrived leaves a finished card claiming to still be running —
209
+ // which is how a failed custom tool ended up reading "Running
210
+ // send_to_partner..." beside its own error output.
211
+ //
212
+ // Only ever used where a result exists, so a genuinely in-flight call is never
213
+ // relabelled. A failure states so plainly; a success keeps whatever specific
214
+ // description the activity text produced ("Reading foo.js"), because that still
215
+ // describes the call accurately, and only the generic present-progressive
216
+ // fallback is replaced.
217
+ export function toolTerminalText(name, input, isError) {
218
+ if (isError) return name + " failed";
219
+ var activity = toolActivityText(name, input);
220
+ return activity === "Running " + name + "..." ? name : activity;
221
+ }
222
+
206
223
  function shortPath(p) {
207
224
  if (!p) return "";
208
225
  var parts = p.split("/");
@@ -2136,9 +2153,12 @@ export function updateToolResult(id, content, isError, images) {
2136
2153
  var tool = tools[id];
2137
2154
  if (!tool) return;
2138
2155
 
2156
+ // Re-render the sublabel from the now-complete input (streaming can have
2157
+ // built it from a partial one), but in its terminal form: a result has
2158
+ // arrived, so this call is no longer running.
2139
2159
  var subtitleText = tool.el.querySelector(".tool-subtitle-text");
2140
2160
  if (subtitleText && tool.input) {
2141
- subtitleText.textContent = toolActivityText(tool.name, tool.input);
2161
+ subtitleText.textContent = toolTerminalText(tool.name, tool.input, isError);
2142
2162
  }
2143
2163
 
2144
2164
  var resultBlock = document.createElement("div");