clay-server 3.0.0 → 3.1.0-beta.2
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/mates-prompts.js +3 -2
- package/lib/notes.js +6 -0
- package/lib/project-http.js +3 -6
- package/lib/project-session-notes.js +217 -0
- package/lib/project.js +84 -18
- package/lib/public/app.js +1 -4
- package/lib/public/css/icon-strip.css +190 -0
- package/lib/public/css/messages.css +104 -2
- package/lib/public/css/mobile-nav.css +0 -4
- package/lib/public/css/pane.css +27 -0
- package/lib/public/css/rewind.css +10 -0
- package/lib/public/css/sticky-notes.css +43 -0
- package/lib/public/css/title-bar.css +0 -169
- package/lib/public/index.html +0 -6
- package/lib/public/modules/app-favicon.js +10 -1
- package/lib/public/modules/app-home-hub.js +0 -2
- package/lib/public/modules/app-messages.js +18 -6
- package/lib/public/modules/app-projects.js +30 -43
- package/lib/public/modules/app-rendering.js +42 -3
- package/lib/public/modules/dom-refs.js +1 -0
- package/lib/public/modules/input.js +13 -0
- package/lib/public/modules/project-switcher.js +1 -2
- package/lib/public/modules/sidebar-mobile.js +6 -19
- package/lib/public/modules/sidebar-projects.js +232 -63
- package/lib/public/modules/split-pair-ui.js +30 -0
- package/lib/public/modules/sticky-note-markdown.js +89 -0
- package/lib/public/modules/sticky-notes.js +56 -103
- package/lib/sdk-bridge.js +25 -1
- package/lib/session-notes-mcp-server.js +39 -0
- package/lib/ws-schema.js +1 -0
- package/lib/yoke/adapters/codex.js +44 -8
- package/lib/yoke/mcp-bridge-server.js +7 -7
- package/package.json +1 -1
- package/lib/public/modules/branch-switcher.js +0 -178
- package/lib/public/modules/worktree-family.js +0 -60
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
2
|
+
import { VENDOR_AVATARS } from './app-rendering.js';
|
|
3
|
+
import { extractMarkdown, getTitle, renderMiniMarkdown } from './sticky-note-markdown.js';
|
|
2
4
|
|
|
3
5
|
var ctx;
|
|
4
6
|
var notes = new Map(); // id -> { data, el }
|
|
@@ -135,106 +137,11 @@ function debouncedTextUpdate(id, text) {
|
|
|
135
137
|
}, 500);
|
|
136
138
|
}
|
|
137
139
|
|
|
138
|
-
// --- Simple markdown ---
|
|
139
|
-
|
|
140
|
-
function getTitle(text) {
|
|
141
|
-
if (!text) return "";
|
|
142
|
-
var idx = text.indexOf("\n");
|
|
143
|
-
return idx === -1 ? text : text.substring(0, idx);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function renderMiniMarkdown(text) {
|
|
147
|
-
if (!text) return "";
|
|
148
|
-
var lines = text.split("\n");
|
|
149
|
-
var title = lines[0];
|
|
150
|
-
var body = lines.slice(1).join("\n");
|
|
151
|
-
|
|
152
|
-
function fmt(s) {
|
|
153
|
-
var escaped = s
|
|
154
|
-
.replace(/&/g, "&")
|
|
155
|
-
.replace(/</g, "<")
|
|
156
|
-
.replace(/>/g, ">");
|
|
157
|
-
return escaped
|
|
158
|
-
.replace(/(https?:\/\/[^\s<]+)/g, '<a href="$1" target="_blank" rel="noopener">$1</a>')
|
|
159
|
-
.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
|
|
160
|
-
.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "<em>$1</em>")
|
|
161
|
-
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
162
|
-
.replace(/~~(.+?)~~/g, "<del>$1</del>")
|
|
163
|
-
.replace(/^- \[x\]/gm, '<span class="sn-check checked">✓</span>')
|
|
164
|
-
.replace(/^- \[ \]/gm, '<span class="sn-check">☐</span>')
|
|
165
|
-
.replace(/\n/g, "<br>");
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
var html = '<div class="sn-title">' + fmt(title) + '</div>';
|
|
169
|
-
if (body.trim()) {
|
|
170
|
-
html += fmt(body);
|
|
171
|
-
}
|
|
172
|
-
return html;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
140
|
function syncTitle(noteEl, text) {
|
|
176
141
|
var spacer = noteEl.querySelector(".sticky-note-spacer");
|
|
177
142
|
if (spacer) spacer.textContent = getTitle(text) || "Untitled";
|
|
178
143
|
}
|
|
179
144
|
|
|
180
|
-
// --- HTML-to-Markdown reverse conversion (for contenteditable) ---
|
|
181
|
-
|
|
182
|
-
function nodeToMd(node) {
|
|
183
|
-
if (node.nodeType === 3) return node.textContent;
|
|
184
|
-
if (node.nodeType !== 1) return "";
|
|
185
|
-
|
|
186
|
-
var tag = node.tagName;
|
|
187
|
-
var inner = childrenToMd(node);
|
|
188
|
-
|
|
189
|
-
switch (tag) {
|
|
190
|
-
case "STRONG": case "B": return "**" + inner + "**";
|
|
191
|
-
case "EM": case "I": return "*" + inner + "*";
|
|
192
|
-
case "DEL": case "S": case "STRIKE": return "~~" + inner + "~~";
|
|
193
|
-
case "CODE": return "`" + inner + "`";
|
|
194
|
-
case "BR": return "\n";
|
|
195
|
-
case "DIV":
|
|
196
|
-
if (node.classList.contains("sn-title")) return inner;
|
|
197
|
-
if (node.classList.contains("sn-placeholder")) return "";
|
|
198
|
-
// Browser-generated div from Enter key = new line
|
|
199
|
-
return "\n" + inner;
|
|
200
|
-
case "P": return "\n" + inner;
|
|
201
|
-
case "A": return node.getAttribute("href") || inner;
|
|
202
|
-
case "SPAN":
|
|
203
|
-
if (node.classList.contains("sn-check")) {
|
|
204
|
-
return node.classList.contains("checked") ? "- [x]" : "- [ ]";
|
|
205
|
-
}
|
|
206
|
-
if (node.classList.contains("sn-placeholder")) return "";
|
|
207
|
-
return inner;
|
|
208
|
-
default: return inner;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function childrenToMd(el) {
|
|
213
|
-
var result = "";
|
|
214
|
-
for (var i = 0; i < el.childNodes.length; i++) {
|
|
215
|
-
result += nodeToMd(el.childNodes[i]);
|
|
216
|
-
}
|
|
217
|
-
return result;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function extractMdFromRendered(rendered) {
|
|
221
|
-
var titleEl = rendered.querySelector(".sn-title");
|
|
222
|
-
if (titleEl) {
|
|
223
|
-
var titleMd = childrenToMd(titleEl);
|
|
224
|
-
var rest = "";
|
|
225
|
-
var afterTitle = false;
|
|
226
|
-
for (var i = 0; i < rendered.childNodes.length; i++) {
|
|
227
|
-
var child = rendered.childNodes[i];
|
|
228
|
-
if (child === titleEl) { afterTitle = true; continue; }
|
|
229
|
-
if (afterTitle) rest += nodeToMd(child);
|
|
230
|
-
}
|
|
231
|
-
if (rest && rest.charAt(0) === "\n") rest = rest.substring(1);
|
|
232
|
-
return titleMd + (rest ? "\n" + rest : "");
|
|
233
|
-
}
|
|
234
|
-
var md = childrenToMd(rendered);
|
|
235
|
-
return md.replace(/^\n+/, "");
|
|
236
|
-
}
|
|
237
|
-
|
|
238
145
|
// --- Note rendering ---
|
|
239
146
|
|
|
240
147
|
function renderNote(data) {
|
|
@@ -256,6 +163,15 @@ function renderNote(data) {
|
|
|
256
163
|
var header = document.createElement("div");
|
|
257
164
|
header.className = "sticky-note-header";
|
|
258
165
|
|
|
166
|
+
if (data.origin && VENDOR_AVATARS[data.origin.vendor]) {
|
|
167
|
+
var originBadge = document.createElement("img");
|
|
168
|
+
originBadge.className = "sticky-note-agent-badge";
|
|
169
|
+
originBadge.src = VENDOR_AVATARS[data.origin.vendor];
|
|
170
|
+
originBadge.alt = "";
|
|
171
|
+
originBadge.title = "Created by " + data.origin.vendor;
|
|
172
|
+
header.appendChild(originBadge);
|
|
173
|
+
}
|
|
174
|
+
|
|
259
175
|
var closeBtn = document.createElement("button");
|
|
260
176
|
closeBtn.className = "sticky-note-btn sticky-note-close";
|
|
261
177
|
closeBtn.title = "Close";
|
|
@@ -603,13 +519,29 @@ function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
|
603
519
|
var noteEl = textarea.closest(".sticky-note");
|
|
604
520
|
var mdMode = false;
|
|
605
521
|
|
|
522
|
+
function saveRenderedMarkdown() {
|
|
523
|
+
var markdown = extractMarkdown(rendered);
|
|
524
|
+
textarea.value = markdown;
|
|
525
|
+
debouncedTextUpdate(noteId, markdown);
|
|
526
|
+
syncTitle(noteEl, markdown);
|
|
527
|
+
rendered.classList.toggle("is-empty", !markdown.trim());
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function toggleChecklistItem(check) {
|
|
531
|
+
var checked = !check.classList.contains("checked");
|
|
532
|
+
check.classList.toggle("checked", checked);
|
|
533
|
+
check.textContent = checked ? "✓" : "☐";
|
|
534
|
+
check.setAttribute("aria-checked", checked ? "true" : "false");
|
|
535
|
+
saveRenderedMarkdown();
|
|
536
|
+
}
|
|
537
|
+
|
|
606
538
|
// MD button: toggle between contenteditable rendered view and raw textarea
|
|
607
539
|
mdBtn.addEventListener("click", function (e) {
|
|
608
540
|
e.stopPropagation();
|
|
609
541
|
mdMode = !mdMode;
|
|
610
542
|
if (mdMode) {
|
|
611
543
|
// Switch to raw markdown editing
|
|
612
|
-
var md =
|
|
544
|
+
var md = extractMarkdown(rendered);
|
|
613
545
|
textarea.value = md;
|
|
614
546
|
textarea.style.display = "";
|
|
615
547
|
rendered.style.display = "none";
|
|
@@ -636,11 +568,15 @@ function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
|
636
568
|
|
|
637
569
|
// Sync contenteditable changes to textarea (data store) and save
|
|
638
570
|
rendered.addEventListener("input", function () {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
571
|
+
saveRenderedMarkdown();
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
rendered.addEventListener("click", function (e) {
|
|
575
|
+
var check = e.target.closest && e.target.closest(".sn-check");
|
|
576
|
+
if (!check || !rendered.contains(check)) return;
|
|
577
|
+
e.preventDefault();
|
|
578
|
+
e.stopPropagation();
|
|
579
|
+
toggleChecklistItem(check);
|
|
644
580
|
});
|
|
645
581
|
|
|
646
582
|
// On blur, re-render to normalize HTML structure
|
|
@@ -648,7 +584,7 @@ function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
|
648
584
|
// Don't re-render if clicking format toolbar (it prevents default, but just in case)
|
|
649
585
|
if (e.relatedTarget && e.relatedTarget.closest && e.relatedTarget.closest(".sn-format-toolbar")) return;
|
|
650
586
|
closeFormatToolbar();
|
|
651
|
-
var md =
|
|
587
|
+
var md = extractMarkdown(rendered);
|
|
652
588
|
textarea.value = md;
|
|
653
589
|
if (md.trim()) {
|
|
654
590
|
rendered.innerHTML = renderMiniMarkdown(md);
|
|
@@ -686,6 +622,12 @@ function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
|
686
622
|
|
|
687
623
|
// Insert <br> on Enter instead of <div>
|
|
688
624
|
rendered.addEventListener("keydown", function (e) {
|
|
625
|
+
if (e.target.classList && e.target.classList.contains("sn-check") &&
|
|
626
|
+
(e.key === "Enter" || e.key === " ")) {
|
|
627
|
+
e.preventDefault();
|
|
628
|
+
toggleChecklistItem(e.target);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
689
631
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
690
632
|
e.preventDefault();
|
|
691
633
|
document.execCommand("insertLineBreak");
|
|
@@ -916,6 +858,17 @@ export function handleNoteUpdated(msg) {
|
|
|
916
858
|
if (archiveOpen) renderArchiveCards();
|
|
917
859
|
}
|
|
918
860
|
|
|
861
|
+
export function handleNoteWritten(msg) {
|
|
862
|
+
var entry = notes.get(msg.id);
|
|
863
|
+
if (!entry) return;
|
|
864
|
+
entry.el.classList.remove("sticky-note-attention");
|
|
865
|
+
void entry.el.offsetWidth;
|
|
866
|
+
entry.el.classList.add("sticky-note-attention");
|
|
867
|
+
setTimeout(function () {
|
|
868
|
+
entry.el.classList.remove("sticky-note-attention");
|
|
869
|
+
}, 2100);
|
|
870
|
+
}
|
|
871
|
+
|
|
919
872
|
export function handleNoteDeleted(msg) {
|
|
920
873
|
var entry = notes.get(msg.id);
|
|
921
874
|
if (!entry) return;
|
|
@@ -958,7 +911,7 @@ function renderArchiveCards() {
|
|
|
958
911
|
if (notes.size === 0) {
|
|
959
912
|
var empty = document.createElement("div");
|
|
960
913
|
empty.className = "notes-archive-empty";
|
|
961
|
-
empty.innerHTML = iconHtml("sticky-note") + "<p>
|
|
914
|
+
empty.innerHTML = iconHtml("sticky-note") + "<p>Keep what matters across sessions</p><p class=\"notes-archive-empty-sub\">Use sticky notes for checklists, goals, handoffs, and durable project knowledge. You and every Clay agent can find them in future sessions.</p><p class=\"notes-archive-empty-sub\">Create one with the " + iconHtml("sticky-note") + " button in the title bar</p>";
|
|
962
915
|
grid.appendChild(empty);
|
|
963
916
|
refreshIcons();
|
|
964
917
|
return;
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -136,7 +136,7 @@ function createSDKBridge(opts) {
|
|
|
136
136
|
"--port", String(clayPort),
|
|
137
137
|
"--slug", slug,
|
|
138
138
|
"--session", String(session.localId),
|
|
139
|
-
"--
|
|
139
|
+
"--session-only",
|
|
140
140
|
];
|
|
141
141
|
if (clayTls) bridgeArgs.push("--tls");
|
|
142
142
|
var env = [];
|
|
@@ -553,6 +553,30 @@ function createSDKBridge(opts) {
|
|
|
553
553
|
return { behavior: "allow", updatedInput: input };
|
|
554
554
|
}
|
|
555
555
|
|
|
556
|
+
// Auto-approve split-pair partner tools. The pair itself is
|
|
557
|
+
// user-constructed (the user created the split and its roles and
|
|
558
|
+
// watches both panes live), the target is structurally confined to the
|
|
559
|
+
// split partner, and a prompt on every delegation hop defeats the
|
|
560
|
+
// driver/worker workflow. spawn_sessions is NOT here: it creates new
|
|
561
|
+
// sessions and keeps its prompt.
|
|
562
|
+
var pairPartnerTools = { send_to_partner: true, read_partner: true };
|
|
563
|
+
if (toolName.indexOf("mcp__clay-sessions__") === 0) {
|
|
564
|
+
var sessionsToolName = toolName.substring(toolName.lastIndexOf("__") + 2);
|
|
565
|
+
if (pairPartnerTools[sessionsToolName]) {
|
|
566
|
+
return { behavior: "allow", updatedInput: input };
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Sticky-note reads and writes are immediately visible on the shared
|
|
571
|
+
// board. Deletion stays outside the whitelist and therefore prompts;
|
|
572
|
+
// its session ownership is enforced again inside the server handler.
|
|
573
|
+
var notesToolName = toolName.substring(toolName.lastIndexOf("__") + 2);
|
|
574
|
+
var isClayNotesTool = toolName.indexOf("mcp__clay-notes__") === 0
|
|
575
|
+
|| toolName.indexOf("clay-notes__") !== -1;
|
|
576
|
+
if (isClayNotesTool && (notesToolName === "list_notes" || notesToolName === "write_note")) {
|
|
577
|
+
return { behavior: "allow", updatedInput: input };
|
|
578
|
+
}
|
|
579
|
+
|
|
556
580
|
// Auto-approve read-only email MCP tools.
|
|
557
581
|
// These only read data from accounts the user explicitly checked.
|
|
558
582
|
// Write operations (send, reply, mark_read) still require permission.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Sticky-note memory tools for project sessions.
|
|
2
|
+
|
|
3
|
+
var buildShape = require("./session-spawn-mcp-server").buildShape;
|
|
4
|
+
|
|
5
|
+
var MEMORY_CONTRACT = "Shared project memory that persists across sessions and is visible to the user and other Clay agents. People may use the board freely. As a Clay agent, use notes specifically for actionable or durable work memory: checklists and to-do lists, work goals, handoffs, unfinished work, durable decisions and constraints, and knowledge that should still be available after the current session ends. Every note starts with a concise plain-text title on the first line. Add a detailed body whenever another worker would otherwise need to reconstruct the context. Keep one coherent topic per note, update an existing note instead of adding a near-duplicate, and delete notes that stop being true. Do not use notes as a transcript, a routine progress log, or an announcement of your own activity.";
|
|
6
|
+
|
|
7
|
+
function getToolDefs(handlers) {
|
|
8
|
+
return [
|
|
9
|
+
{
|
|
10
|
+
name: "list_notes",
|
|
11
|
+
description: MEMORY_CONTRACT + " List the active notes before writing when you need to avoid duplicates or inspect full memory beyond the injected summary.",
|
|
12
|
+
inputSchema: buildShape({}),
|
|
13
|
+
handler: function (args) { return handlers.list(args || {}); },
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "write_note",
|
|
17
|
+
description: MEMORY_CONTRACT + " Create a new note, or update an existing note by id. Long task and handoff notes are allowed, with a generous 20000-character abuse guard.",
|
|
18
|
+
inputSchema: buildShape({
|
|
19
|
+
id: { type: "string", description: "Existing note id to update. Omit to create a note." },
|
|
20
|
+
text: { type: "string", description: "Sticky-note text. Put the title on the first line, followed by the detailed handoff body. The board permits up to 20000 characters." },
|
|
21
|
+
color: { type: "string", enum: ["yellow", "blue", "green", "pink", "orange", "purple"], description: "Optional sticky-note color." },
|
|
22
|
+
}, ["text"]),
|
|
23
|
+
handler: function (args) { return handlers.write(args || {}); },
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "remove_note",
|
|
27
|
+
description: MEMORY_CONTRACT + " Remove a note created by this same session when it is no longer true. Notes created by users or other sessions cannot be removed.",
|
|
28
|
+
inputSchema: buildShape({
|
|
29
|
+
id: { type: "string", description: "Id of the note to remove." },
|
|
30
|
+
}, ["id"]),
|
|
31
|
+
handler: function (args) { return handlers.remove(args || {}); },
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
MEMORY_CONTRACT: MEMORY_CONTRACT,
|
|
38
|
+
getToolDefs: getToolDefs,
|
|
39
|
+
};
|
package/lib/ws-schema.js
CHANGED
|
@@ -355,6 +355,7 @@ var schema = {
|
|
|
355
355
|
// -----------------------------------------------------------------------
|
|
356
356
|
"note_create": { direction: "c2s", handler: "lib/project-user-message.js", description: "Create a new sticky note" },
|
|
357
357
|
"note_created": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Sticky note was created" },
|
|
358
|
+
"note_written": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Agent created or updated a sticky note" },
|
|
358
359
|
"note_update": { direction: "c2s", handler: "lib/project-user-message.js", description: "Update a sticky note" },
|
|
359
360
|
"note_updated": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Sticky note was updated" },
|
|
360
361
|
"note_delete": { direction: "c2s", handler: "lib/project-user-message.js", description: "Delete a sticky note" },
|
|
@@ -97,6 +97,18 @@ function normalizePlanStatus(status) {
|
|
|
97
97
|
return "pending";
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
function dynamicInputSchema(inputSchema) {
|
|
101
|
+
if (inputSchema && typeof inputSchema.type === "string") return inputSchema;
|
|
102
|
+
try {
|
|
103
|
+
var zod = require("zod");
|
|
104
|
+
if (zod.toJSONSchema && inputSchema) {
|
|
105
|
+
var schema = inputSchema.safeParse ? inputSchema : zod.object(inputSchema);
|
|
106
|
+
return zod.toJSONSchema(schema);
|
|
107
|
+
}
|
|
108
|
+
} catch (e) {}
|
|
109
|
+
return { type: "object", properties: {} };
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
// Detect Codex "not logged in" errors. Codex surfaces auth failures several
|
|
101
113
|
// ways depending on transport: a clean error event with
|
|
102
114
|
// codexErrorInfo:"unauthorized", or a turn/failed / item error whose message
|
|
@@ -813,15 +825,39 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
813
825
|
});
|
|
814
826
|
return;
|
|
815
827
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
828
|
+
function executeDynamicTool() {
|
|
829
|
+
return Promise.resolve(callDynamicTool(dynamicParams.tool, dynamicParams.arguments || {})).then(function (result) {
|
|
830
|
+
var content = result && Array.isArray(result.content) ? result.content : [];
|
|
831
|
+
var contentItems = content.map(function (item) {
|
|
832
|
+
return { type: "inputText", text: item && item.text ? item.text : JSON.stringify(item) };
|
|
833
|
+
});
|
|
834
|
+
if (contentItems.length === 0) {
|
|
835
|
+
contentItems.push({ type: "inputText", text: typeof result === "string" ? result : JSON.stringify(result) });
|
|
836
|
+
}
|
|
837
|
+
appServer.respond(msg.id, { contentItems: contentItems, success: !(result && result.isError) });
|
|
820
838
|
});
|
|
821
|
-
|
|
822
|
-
|
|
839
|
+
}
|
|
840
|
+
var permissionToolName = dynamicParams.tool;
|
|
841
|
+
if (permissionToolName === "list_notes" || permissionToolName === "write_note" || permissionToolName === "remove_note") {
|
|
842
|
+
permissionToolName = "mcp__clay-notes__" + permissionToolName;
|
|
843
|
+
} else if (permissionToolName === "send_to_partner" || permissionToolName === "read_partner") {
|
|
844
|
+
permissionToolName = "mcp__clay-sessions__" + permissionToolName;
|
|
845
|
+
}
|
|
846
|
+
var permission = canUseTool
|
|
847
|
+
? Promise.resolve(canUseTool(permissionToolName, dynamicParams.arguments || {}, {
|
|
848
|
+
toolUseID: dynamicParams.callId || String(msg.id),
|
|
849
|
+
signal: abortController ? abortController.signal : null,
|
|
850
|
+
}))
|
|
851
|
+
: Promise.resolve({ behavior: "allow" });
|
|
852
|
+
permission.then(function (decision) {
|
|
853
|
+
if (!isApproved(decision)) {
|
|
854
|
+
appServer.respond(msg.id, {
|
|
855
|
+
contentItems: [{ type: "inputText", text: "Tool use was not approved" }],
|
|
856
|
+
success: false,
|
|
857
|
+
});
|
|
858
|
+
return;
|
|
823
859
|
}
|
|
824
|
-
|
|
860
|
+
return executeDynamicTool();
|
|
825
861
|
}).catch(function (err) {
|
|
826
862
|
appServer.respond(msg.id, {
|
|
827
863
|
contentItems: [{ type: "inputText", text: "Error: " + (err.message || String(err)) }],
|
|
@@ -963,7 +999,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
963
999
|
type: "function",
|
|
964
1000
|
name: tool.name,
|
|
965
1001
|
description: tool.description || tool.name,
|
|
966
|
-
inputSchema: tool.inputSchema
|
|
1002
|
+
inputSchema: dynamicInputSchema(tool.inputSchema),
|
|
967
1003
|
};
|
|
968
1004
|
});
|
|
969
1005
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// list/call requests to Clay's project-scoped HTTP endpoint
|
|
7
7
|
// (/p/{slug}/api/mcp-bridge) when a slug is provided.
|
|
8
8
|
//
|
|
9
|
-
// Usage: node mcp-bridge-server.js --port 2633 --slug my-project [--session 42] [--
|
|
9
|
+
// Usage: node mcp-bridge-server.js --port 2633 --slug my-project [--session 42] [--session-only]
|
|
10
10
|
//
|
|
11
11
|
// Lifecycle:
|
|
12
12
|
// 1. The vendor CLI spawns this process
|
|
@@ -25,7 +25,7 @@ var clayPort = 2633;
|
|
|
25
25
|
var claySlug = "";
|
|
26
26
|
var clayTls = false;
|
|
27
27
|
var claySessionId = null;
|
|
28
|
-
var
|
|
28
|
+
var sessionOnly = false;
|
|
29
29
|
|
|
30
30
|
for (var i = 0; i < args.length; i++) {
|
|
31
31
|
if (args[i] === "--port" && args[i + 1]) {
|
|
@@ -40,8 +40,8 @@ for (var i = 0; i < args.length; i++) {
|
|
|
40
40
|
i++;
|
|
41
41
|
} else if (args[i] === "--tls") {
|
|
42
42
|
clayTls = true;
|
|
43
|
-
} else if (args[i] === "--
|
|
44
|
-
|
|
43
|
+
} else if (args[i] === "--session-only") {
|
|
44
|
+
sessionOnly = true;
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -119,7 +119,7 @@ function postJson(urlPath, body) {
|
|
|
119
119
|
|
|
120
120
|
// --- Fetch tools from Clay ---
|
|
121
121
|
function fetchTools() {
|
|
122
|
-
return postJson(CLAY_MCP_PATH, { action: "list_tools", sessionId: claySessionId,
|
|
122
|
+
return postJson(CLAY_MCP_PATH, { action: "list_tools", sessionId: claySessionId, sessionOnly: sessionOnly }).then(function (resp) {
|
|
123
123
|
if (resp.error) {
|
|
124
124
|
log("Failed to fetch tools: " + resp.error);
|
|
125
125
|
return [];
|
|
@@ -146,7 +146,7 @@ function callTool(serverName, toolName, args) {
|
|
|
146
146
|
tool: toolName,
|
|
147
147
|
args: args || {},
|
|
148
148
|
sessionId: claySessionId,
|
|
149
|
-
|
|
149
|
+
sessionOnly: sessionOnly,
|
|
150
150
|
});
|
|
151
151
|
}
|
|
152
152
|
|
|
@@ -308,4 +308,4 @@ process.stdin.on("error", function () {
|
|
|
308
308
|
process.on("SIGTERM", function () { process.exit(0); });
|
|
309
309
|
process.on("SIGINT", function () { process.exit(0); });
|
|
310
310
|
|
|
311
|
-
log("Started: port=" + clayPort + " slug=" + claySlug + " session=" + (claySessionId === null ? "none" : claySessionId) + "
|
|
311
|
+
log("Started: port=" + clayPort + " slug=" + claySlug + " session=" + (claySessionId === null ? "none" : claySessionId) + " sessionOnly=" + sessionOnly);
|
package/package.json
CHANGED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
// Title-bar branch switcher for a project and its worktrees.
|
|
2
|
-
|
|
3
|
-
import { store } from './store.js';
|
|
4
|
-
import { iconHtml, refreshIcons } from './icons.js';
|
|
5
|
-
import { escapeHtml } from './utils.js';
|
|
6
|
-
import { familyOf } from './worktree-family.js';
|
|
7
|
-
import { switchProject, confirmRemoveProject } from './app-projects.js';
|
|
8
|
-
import { showWorktreeModal } from './sidebar-projects.js';
|
|
9
|
-
|
|
10
|
-
var chip = null;
|
|
11
|
-
var label = null;
|
|
12
|
-
var menu = null;
|
|
13
|
-
var menuOpen = false;
|
|
14
|
-
var defaultBranches = {};
|
|
15
|
-
// slug -> true/false once /api/branches answers; undefined while unknown.
|
|
16
|
-
// Non-git projects hide the chip (no worktrees to manage there).
|
|
17
|
-
var gitRepoBySlug = {};
|
|
18
|
-
|
|
19
|
-
function projectName(project) {
|
|
20
|
-
return project ? (project.title || project.project || project.name || project.slug) : "";
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function currentProject(projects) {
|
|
24
|
-
var slug = store.get('currentSlug');
|
|
25
|
-
for (var i = 0; i < projects.length; i++) {
|
|
26
|
-
if (projects[i].slug === slug) return projects[i];
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function defaultBranch(parent) {
|
|
32
|
-
return defaultBranches[parent.slug] || "default";
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function requestDefaultBranch(parent) {
|
|
36
|
-
if (!parent || Object.prototype.hasOwnProperty.call(defaultBranches, parent.slug)) return;
|
|
37
|
-
defaultBranches[parent.slug] = null;
|
|
38
|
-
fetch("/p/" + encodeURIComponent(parent.slug) + "/api/branches")
|
|
39
|
-
.then(function (response) { return response.json(); })
|
|
40
|
-
.then(function (data) {
|
|
41
|
-
defaultBranches[parent.slug] = data.defaultBranch || "default";
|
|
42
|
-
gitRepoBySlug[parent.slug] = data.isGitRepo !== false;
|
|
43
|
-
renderBranchSwitcher();
|
|
44
|
-
})
|
|
45
|
-
.catch(function () {
|
|
46
|
-
defaultBranches[parent.slug] = "default";
|
|
47
|
-
renderBranchSwitcher();
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function closeMenu() {
|
|
52
|
-
menuOpen = false;
|
|
53
|
-
if (menu) menu.classList.add("hidden");
|
|
54
|
-
if (chip) {
|
|
55
|
-
chip.classList.remove("open");
|
|
56
|
-
chip.setAttribute("aria-expanded", "false");
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function positionMenu() {
|
|
61
|
-
if (!chip || !menu || !menuOpen) return;
|
|
62
|
-
var rect = chip.getBoundingClientRect();
|
|
63
|
-
var left = Math.min(rect.left, window.innerWidth - menu.offsetWidth - 8);
|
|
64
|
-
var top = rect.bottom + 6;
|
|
65
|
-
if (top + menu.offsetHeight > window.innerHeight - 8) top = rect.top - menu.offsetHeight - 6;
|
|
66
|
-
menu.style.left = Math.max(8, left) + "px";
|
|
67
|
-
menu.style.top = Math.max(8, top) + "px";
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function branchLabel(project, parent) {
|
|
71
|
-
return project.isWorktree ? (project.branch || projectName(project)) : defaultBranch(parent);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function appendProjectRow(container, project, parent, isCurrent) {
|
|
75
|
-
var row = document.createElement("div");
|
|
76
|
-
row.className = "branch-chip-row" + (isCurrent ? " current" : "");
|
|
77
|
-
var inaccessible = project.isWorktree && project.worktreeAccessible === false;
|
|
78
|
-
var main = document.createElement("button");
|
|
79
|
-
main.className = "branch-chip-row-main";
|
|
80
|
-
main.disabled = inaccessible;
|
|
81
|
-
if (inaccessible) main.title = "Outside project path";
|
|
82
|
-
main.innerHTML = iconHtml("git-branch") + '<span class="branch-chip-row-label">' +
|
|
83
|
-
escapeHtml(branchLabel(project, parent)) + "</span>";
|
|
84
|
-
if (project.isProcessing) {
|
|
85
|
-
var dot = document.createElement("span");
|
|
86
|
-
dot.className = "branch-chip-processing";
|
|
87
|
-
main.appendChild(dot);
|
|
88
|
-
}
|
|
89
|
-
if (project.pendingPermissions > 0) {
|
|
90
|
-
var badge = document.createElement("span");
|
|
91
|
-
badge.className = "branch-chip-pending";
|
|
92
|
-
badge.textContent = project.pendingPermissions > 99 ? "99+" : String(project.pendingPermissions);
|
|
93
|
-
main.appendChild(badge);
|
|
94
|
-
}
|
|
95
|
-
if (isCurrent) main.insertAdjacentHTML("beforeend", iconHtml("check"));
|
|
96
|
-
if (!inaccessible) {
|
|
97
|
-
main.addEventListener("click", function () { closeMenu(); switchProject(project.slug); });
|
|
98
|
-
}
|
|
99
|
-
row.appendChild(main);
|
|
100
|
-
|
|
101
|
-
if (project.isWorktree && (!store.get('permissions') || store.get('permissions').deleteProject !== false)) {
|
|
102
|
-
var remove = document.createElement("button");
|
|
103
|
-
remove.className = "branch-chip-row-action";
|
|
104
|
-
remove.title = "Remove worktree";
|
|
105
|
-
remove.innerHTML = iconHtml("trash-2");
|
|
106
|
-
remove.addEventListener("click", function (event) {
|
|
107
|
-
event.stopPropagation();
|
|
108
|
-
closeMenu();
|
|
109
|
-
confirmRemoveProject(project.slug, projectName(project));
|
|
110
|
-
});
|
|
111
|
-
row.appendChild(remove);
|
|
112
|
-
}
|
|
113
|
-
container.appendChild(row);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function renderMenu(family, current) {
|
|
117
|
-
menu.innerHTML = "";
|
|
118
|
-
if (family.parent) appendProjectRow(menu, family.parent, family.parent, current.slug === family.parent.slug);
|
|
119
|
-
for (var i = 0; i < family.worktrees.length; i++) {
|
|
120
|
-
appendProjectRow(menu, family.worktrees[i], family.parent || family.worktrees[i], current.slug === family.worktrees[i].slug);
|
|
121
|
-
}
|
|
122
|
-
if (family.parent) {
|
|
123
|
-
var footer = document.createElement("button");
|
|
124
|
-
footer.className = "branch-chip-new";
|
|
125
|
-
footer.innerHTML = iconHtml("plus") + " <span>New worktree...</span>";
|
|
126
|
-
footer.addEventListener("click", function () {
|
|
127
|
-
closeMenu();
|
|
128
|
-
showWorktreeModal(family.parent.slug, projectName(family.parent));
|
|
129
|
-
});
|
|
130
|
-
menu.appendChild(footer);
|
|
131
|
-
}
|
|
132
|
-
refreshIcons();
|
|
133
|
-
positionMenu();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function renderBranchSwitcher() {
|
|
137
|
-
if (!chip) return;
|
|
138
|
-
var projects = store.get('projects') || [];
|
|
139
|
-
var current = currentProject(projects);
|
|
140
|
-
var family = current ? familyOf(projects, current.slug) : { parent: null, worktrees: [] };
|
|
141
|
-
// The chip is the project's worktree management surface, so it shows even
|
|
142
|
-
// with zero worktrees (the menu then offers the default branch and "New
|
|
143
|
-
// worktree"). Hidden where there is no project context to manage, and for
|
|
144
|
-
// non-git projects (optimistically visible until /api/branches answers).
|
|
145
|
-
var gateSlug = family.parent ? family.parent.slug : (current ? current.slug : null);
|
|
146
|
-
var hidden = !current || store.get('dmMode') || store.get('mateProjectSlug') ||
|
|
147
|
-
store.get('homeHubVisible') || (gateSlug && gitRepoBySlug[gateSlug] === false);
|
|
148
|
-
chip.classList.toggle("hidden", hidden);
|
|
149
|
-
if (hidden) { closeMenu(); return; }
|
|
150
|
-
if (family.parent) requestDefaultBranch(family.parent);
|
|
151
|
-
label.textContent = branchLabel(current, family.parent || current);
|
|
152
|
-
if (menuOpen) renderMenu(family, current);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export function initBranchSwitcher() {
|
|
156
|
-
chip = document.getElementById("branch-chip");
|
|
157
|
-
label = document.getElementById("branch-chip-label");
|
|
158
|
-
menu = document.getElementById("branch-chip-menu");
|
|
159
|
-
if (!chip || !label || !menu) return;
|
|
160
|
-
chip.addEventListener("click", function (event) {
|
|
161
|
-
event.stopPropagation();
|
|
162
|
-
menuOpen = !menuOpen;
|
|
163
|
-
chip.classList.toggle("open", menuOpen);
|
|
164
|
-
chip.setAttribute("aria-expanded", menuOpen ? "true" : "false");
|
|
165
|
-
menu.classList.toggle("hidden", !menuOpen);
|
|
166
|
-
renderBranchSwitcher();
|
|
167
|
-
});
|
|
168
|
-
document.addEventListener("click", function (event) {
|
|
169
|
-
if (menuOpen && !menu.contains(event.target) && !chip.contains(event.target)) closeMenu();
|
|
170
|
-
});
|
|
171
|
-
window.addEventListener("resize", positionMenu);
|
|
172
|
-
store.subscribe(function (state, prev) {
|
|
173
|
-
if (state.projects !== prev.projects || state.currentSlug !== prev.currentSlug ||
|
|
174
|
-
state.dmMode !== prev.dmMode || state.mateProjectSlug !== prev.mateProjectSlug ||
|
|
175
|
-
state.homeHubVisible !== prev.homeHubVisible) renderBranchSwitcher();
|
|
176
|
-
});
|
|
177
|
-
renderBranchSwitcher();
|
|
178
|
-
}
|