clay-server 3.0.0 → 3.1.0-beta.1
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/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 +18 -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-notes.js +21 -0
- 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
package/lib/notes.js
CHANGED
|
@@ -59,6 +59,12 @@ function createNotesManager(opts) {
|
|
|
59
59
|
createdAt: now,
|
|
60
60
|
updatedAt: now,
|
|
61
61
|
};
|
|
62
|
+
if (data.origin && data.origin.sessionId !== undefined) {
|
|
63
|
+
note.origin = {
|
|
64
|
+
sessionId: data.origin.sessionId,
|
|
65
|
+
vendor: data.origin.vendor || "claude",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
62
68
|
notes.push(note);
|
|
63
69
|
saveToDisk();
|
|
64
70
|
return note;
|
package/lib/project-http.js
CHANGED
|
@@ -676,13 +676,10 @@ function attachHTTP(ctx) {
|
|
|
676
676
|
defBr = hrRef.replace(/^origin\//, "");
|
|
677
677
|
} catch (e) {}
|
|
678
678
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
679
|
-
res.end(JSON.stringify({ branches: brList, defaultBranch: defBr
|
|
679
|
+
res.end(JSON.stringify({ branches: brList, defaultBranch: defBr }));
|
|
680
680
|
} catch (e) {
|
|
681
|
-
// git failed: not a repository (or git missing). The branch chip
|
|
682
|
-
// hides itself on isGitRepo:false; the fallback branch list keeps
|
|
683
|
-
// the worktree modal from crashing if it is somehow reached.
|
|
684
681
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
685
|
-
res.end(JSON.stringify({ branches: ["main"], defaultBranch: "main"
|
|
682
|
+
res.end(JSON.stringify({ branches: ["main"], defaultBranch: "main" }));
|
|
686
683
|
}
|
|
687
684
|
return true;
|
|
688
685
|
}
|
|
@@ -700,7 +697,7 @@ function attachHTTP(ctx) {
|
|
|
700
697
|
}
|
|
701
698
|
var sessionId = Number(body.sessionId);
|
|
702
699
|
if (!Number.isInteger(sessionId)) sessionId = null;
|
|
703
|
-
var handler = getMcpBridgeHandler(sessionId, body.
|
|
700
|
+
var handler = getMcpBridgeHandler(sessionId, body.sessionOnly === true);
|
|
704
701
|
if (!handler) {
|
|
705
702
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
706
703
|
res.end('{"error":"MCP bridge handler unavailable"}');
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
var sessionNotesMcp = require("./session-notes-mcp-server");
|
|
2
|
+
|
|
3
|
+
var MAX_NOTE_TEXT_CHARS = 2000;
|
|
4
|
+
var MAX_ACTIVE_NOTES = 20;
|
|
5
|
+
var MAX_PROMPT_CHARS = 2000;
|
|
6
|
+
var MAX_NOTE_PROMPT_CHARS = 240;
|
|
7
|
+
var NOTES_LABEL = "--- Project sticky notes (shared memory; manage via clay-notes tools) ---";
|
|
8
|
+
var NOTES_HINT = "- More notes are available; call list_notes for the rest.";
|
|
9
|
+
var PROACTIVE_POLICY = "Use the board proactively: when the user states a decision, preference, correction, or durable project fact that future sessions will need, record it with write_note WITHOUT being asked, and say so in one short clause. The bar: a person skimming the board a week from now must still find the note useful, and it must stand on its own without this conversation. Never write announcement or narration notes ('leaving a note', 'did X just now'), routine progress, transient state, or anything the repo already records. Update or remove your stale notes instead of adding near-duplicates. A task should rarely add more than one or two notes.";
|
|
10
|
+
var NOTE_COLORS = ["yellow", "blue", "green", "pink", "orange", "purple"];
|
|
11
|
+
|
|
12
|
+
function toolResult(value) {
|
|
13
|
+
return Promise.resolve({ content: [{ type: "text", text: JSON.stringify(value) }] });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function toolError(message) {
|
|
17
|
+
return Promise.resolve({
|
|
18
|
+
content: [{ type: "text", text: "Error: " + message }],
|
|
19
|
+
isError: true,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function activeNotes(notes) {
|
|
24
|
+
return (notes || []).filter(function (note) {
|
|
25
|
+
return note && !note.hidden;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function memoryNotes(notes) {
|
|
30
|
+
return activeNotes(notes).filter(function (note) {
|
|
31
|
+
return typeof note.text === "string" && note.text.trim();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function publicNote(note) {
|
|
36
|
+
return {
|
|
37
|
+
id: note.id,
|
|
38
|
+
text: note.text,
|
|
39
|
+
color: note.color,
|
|
40
|
+
updatedAt: note.updatedAt,
|
|
41
|
+
origin: note.origin || null,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function findNote(notes, id) {
|
|
46
|
+
for (var i = 0; i < notes.length; i++) {
|
|
47
|
+
if (notes[i] && notes[i].id === id) return notes[i];
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function autoPlacement(notes) {
|
|
53
|
+
if (!notes || notes.length === 0) return { x: 100, y: 100 };
|
|
54
|
+
var last = notes[notes.length - 1];
|
|
55
|
+
var baseX = typeof last.x === "number" ? last.x : 100;
|
|
56
|
+
var baseY = typeof last.y === "number" ? last.y : 100;
|
|
57
|
+
var step = 1;
|
|
58
|
+
while (step <= notes.length + 1) {
|
|
59
|
+
var x = baseX + step * 30;
|
|
60
|
+
var y = baseY + step * 30;
|
|
61
|
+
var occupied = notes.some(function (note) {
|
|
62
|
+
return note && note.x === x && note.y === y;
|
|
63
|
+
});
|
|
64
|
+
if (!occupied) return { x: x, y: y };
|
|
65
|
+
step++;
|
|
66
|
+
}
|
|
67
|
+
return { x: baseX + (notes.length + 2) * 30, y: baseY + (notes.length + 2) * 30 };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function formatNoteLine(note) {
|
|
71
|
+
var prefix = "- " + (note.color ? "[" + note.color + "] " : "");
|
|
72
|
+
var text = note.text.trim();
|
|
73
|
+
var marker = "… (list_notes for the full note)";
|
|
74
|
+
if (prefix.length + text.length <= MAX_NOTE_PROMPT_CHARS) return prefix + text;
|
|
75
|
+
var previewLength = Math.max(0, MAX_NOTE_PROMPT_CHARS - prefix.length - marker.length);
|
|
76
|
+
return prefix + text.slice(0, previewLength).trimEnd() + marker;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildNotesPrompt(notes) {
|
|
80
|
+
var newest = memoryNotes(notes).sort(function (a, b) {
|
|
81
|
+
return (b.updatedAt || b.createdAt || 0) - (a.updatedAt || a.createdAt || 0);
|
|
82
|
+
});
|
|
83
|
+
if (newest.length === 0) return NOTES_LABEL + "\n(board is empty)\n" + PROACTIVE_POLICY;
|
|
84
|
+
var lines = newest.map(formatNoteLine);
|
|
85
|
+
var full = NOTES_LABEL + "\n" + lines.join("\n");
|
|
86
|
+
if (full.length <= MAX_PROMPT_CHARS) return full + "\n" + PROACTIVE_POLICY;
|
|
87
|
+
|
|
88
|
+
var result = NOTES_LABEL;
|
|
89
|
+
var available = MAX_PROMPT_CHARS - result.length - NOTES_HINT.length - 2;
|
|
90
|
+
for (var i = 0; i < lines.length && available > 0; i++) {
|
|
91
|
+
var prefix = "\n";
|
|
92
|
+
if (prefix.length + lines[i].length <= available) {
|
|
93
|
+
result += prefix + lines[i];
|
|
94
|
+
available -= prefix.length + lines[i].length;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
var partialLength = Math.max(0, available - prefix.length - 1);
|
|
98
|
+
if (partialLength > 0) result += prefix + lines[i].slice(0, partialLength) + "…";
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
return (result + "\n" + NOTES_HINT).slice(0, MAX_PROMPT_CHARS) + "\n" + PROACTIVE_POLICY;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function composeSystemPrompts(parts) {
|
|
105
|
+
return (parts || []).filter(function (part) { return typeof part === "string" && part.trim(); }).join("\n\n");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function attachSessionNotes(ctx) {
|
|
109
|
+
var nm = ctx.nm;
|
|
110
|
+
var send = ctx.send || function () {};
|
|
111
|
+
var broadcastWritten = ctx.broadcastWritten || function () {};
|
|
112
|
+
|
|
113
|
+
function notifyWritten(note, caller) {
|
|
114
|
+
broadcastWritten({
|
|
115
|
+
type: "note_written",
|
|
116
|
+
id: note.id,
|
|
117
|
+
byTitle: caller.title || "Agent",
|
|
118
|
+
vendor: caller.vendor || "claude",
|
|
119
|
+
preview: (note.text || "").slice(0, 60),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function listNotes(args, caller) {
|
|
124
|
+
if (!caller) return toolError("list_notes requires a session-bound tool server");
|
|
125
|
+
return toolResult(activeNotes(nm.list()).map(publicNote));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function writeNote(args, caller) {
|
|
129
|
+
if (!caller) return toolError("write_note requires a session-bound tool server");
|
|
130
|
+
var rawText = typeof args.text === "string" ? args.text : "";
|
|
131
|
+
if (rawText.length > MAX_NOTE_TEXT_CHARS) return toolError("text exceeds 2000 characters");
|
|
132
|
+
var text = rawText.trim();
|
|
133
|
+
if (!text) return toolError("text is required");
|
|
134
|
+
var notes = nm.list() || [];
|
|
135
|
+
var color = NOTE_COLORS.indexOf(args.color) !== -1 ? args.color : undefined;
|
|
136
|
+
if (args.id) {
|
|
137
|
+
var existing = findNote(notes, args.id);
|
|
138
|
+
if (!existing) return toolError("note not found: " + args.id);
|
|
139
|
+
var changes = { text: text };
|
|
140
|
+
if (color) changes.color = color;
|
|
141
|
+
var updated = nm.update(args.id, changes);
|
|
142
|
+
if (!updated) return toolError("note could not be updated: " + args.id);
|
|
143
|
+
send({ type: "note_updated", note: updated });
|
|
144
|
+
notifyWritten(updated, caller);
|
|
145
|
+
return toolResult(publicNote(updated));
|
|
146
|
+
}
|
|
147
|
+
if (activeNotes(notes).length >= MAX_ACTIVE_NOTES) {
|
|
148
|
+
return toolError("20 active notes already exist; consolidate or remove stale notes before creating another");
|
|
149
|
+
}
|
|
150
|
+
var placement = autoPlacement(notes);
|
|
151
|
+
var created = nm.create({
|
|
152
|
+
text: text,
|
|
153
|
+
color: color || "yellow",
|
|
154
|
+
x: placement.x,
|
|
155
|
+
y: placement.y,
|
|
156
|
+
origin: { sessionId: caller.localId, vendor: caller.vendor || "claude" },
|
|
157
|
+
});
|
|
158
|
+
if (!created) return toolError("note could not be created");
|
|
159
|
+
send({ type: "note_created", note: created });
|
|
160
|
+
notifyWritten(created, caller);
|
|
161
|
+
return toolResult(publicNote(created));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function removeNote(args, caller) {
|
|
165
|
+
if (!caller) return toolError("remove_note requires a session-bound tool server");
|
|
166
|
+
var note = findNote(nm.list() || [], args.id);
|
|
167
|
+
if (!note) return toolError("note not found: " + (args.id || "unknown"));
|
|
168
|
+
if (!note.origin || note.origin.sessionId !== caller.localId) {
|
|
169
|
+
return toolError("this session can only remove notes it created");
|
|
170
|
+
}
|
|
171
|
+
if (!nm.remove(note.id)) return toolError("note could not be removed: " + note.id);
|
|
172
|
+
send({ type: "note_deleted", id: note.id });
|
|
173
|
+
return toolResult({ removed: true, id: note.id });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getToolDefs(boundSession) {
|
|
177
|
+
if (ctx.isMate) return [];
|
|
178
|
+
return sessionNotesMcp.getToolDefs({
|
|
179
|
+
list: function (args) { return listNotes(args, boundSession || null); },
|
|
180
|
+
write: function (args) { return writeNote(args, boundSession || null); },
|
|
181
|
+
remove: function (args) { return removeNote(args, boundSession || null); },
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function createMcpServer(adapter, boundSession) {
|
|
186
|
+
if (ctx.isMate || !adapter || typeof adapter.createToolServer !== "function") return null;
|
|
187
|
+
return adapter.createToolServer({
|
|
188
|
+
name: "clay-notes",
|
|
189
|
+
version: "1.0.0",
|
|
190
|
+
tools: getToolDefs(boundSession || null),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function getSystemPrompt() {
|
|
195
|
+
if (ctx.isMate) return "";
|
|
196
|
+
return buildNotesPrompt(nm.list());
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
createMcpServer: createMcpServer,
|
|
201
|
+
getSystemPrompt: getSystemPrompt,
|
|
202
|
+
getToolDefs: getToolDefs,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
module.exports = {
|
|
207
|
+
MAX_NOTE_TEXT_CHARS: MAX_NOTE_TEXT_CHARS,
|
|
208
|
+
MAX_ACTIVE_NOTES: MAX_ACTIVE_NOTES,
|
|
209
|
+
MAX_NOTE_PROMPT_CHARS: MAX_NOTE_PROMPT_CHARS,
|
|
210
|
+
MAX_PROMPT_CHARS: MAX_PROMPT_CHARS,
|
|
211
|
+
NOTES_LABEL: NOTES_LABEL,
|
|
212
|
+
PROACTIVE_POLICY: PROACTIVE_POLICY,
|
|
213
|
+
attachSessionNotes: attachSessionNotes,
|
|
214
|
+
autoPlacement: autoPlacement,
|
|
215
|
+
buildNotesPrompt: buildNotesPrompt,
|
|
216
|
+
composeSystemPrompts: composeSystemPrompts,
|
|
217
|
+
};
|
package/lib/project.js
CHANGED
|
@@ -32,6 +32,7 @@ var { createLocalMcp } = require("./mcp-local");
|
|
|
32
32
|
var { attachEmail: attachEmailModule } = require("./project-email");
|
|
33
33
|
var { attachSessionSpawn } = require("./project-session-spawn");
|
|
34
34
|
var { attachSessionPair } = require("./project-session-pair");
|
|
35
|
+
var { attachSessionNotes, composeSystemPrompts } = require("./project-session-notes");
|
|
35
36
|
var { attachSplitGroups } = require("./session-split-groups");
|
|
36
37
|
// project-notifications is attached globally in server.js, passed via opts.notificationsModule
|
|
37
38
|
|
|
@@ -492,6 +493,21 @@ function createProjectContext(opts) {
|
|
|
492
493
|
},
|
|
493
494
|
});
|
|
494
495
|
|
|
496
|
+
// Sticky-note storage is initialized before session tool wiring so every
|
|
497
|
+
// vendor receives the same handlers and prompt snapshot at query start.
|
|
498
|
+
var nm = createNotesManager({ cwd: cwd, send: send, sendTo: sendTo });
|
|
499
|
+
var _sessionNotes = attachSessionNotes({
|
|
500
|
+
nm: nm,
|
|
501
|
+
send: send,
|
|
502
|
+
isMate: isMate,
|
|
503
|
+
broadcastWritten: function (message) {
|
|
504
|
+
for (var noteWs of clients) {
|
|
505
|
+
if (noteWs.readyState !== 1 || noteWs._clayPane) continue;
|
|
506
|
+
sendTo(noteWs, message);
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
|
|
495
511
|
// The SDK bridge is created after local MCP servers. Session spawning uses
|
|
496
512
|
// a getter so tool handlers see the initialized bridge when they run.
|
|
497
513
|
var _sessionPair = attachSessionPair({
|
|
@@ -539,6 +555,16 @@ function createProjectContext(opts) {
|
|
|
539
555
|
}
|
|
540
556
|
}
|
|
541
557
|
|
|
558
|
+
// Shared sticky-note memory (main projects only).
|
|
559
|
+
if (!isMate) {
|
|
560
|
+
try {
|
|
561
|
+
var sessionNotesMcpConfig = _sessionNotes.createMcpServer(adapter);
|
|
562
|
+
if (sessionNotesMcpConfig) servers[sessionNotesMcpConfig.name || "clay-notes"] = sessionNotesMcpConfig;
|
|
563
|
+
} catch (e) {
|
|
564
|
+
console.error("[project] Failed to create session notes MCP server:", e.message);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
542
568
|
// Debate MCP server (available to both mates and main project)
|
|
543
569
|
try {
|
|
544
570
|
var debateMcp = require("./debate-mcp-server");
|
|
@@ -702,15 +728,14 @@ function createProjectContext(opts) {
|
|
|
702
728
|
// clay-email -> only when the user has an account or server SMTP
|
|
703
729
|
//
|
|
704
730
|
// forSession (optional): the session whose query these servers are mounted
|
|
705
|
-
// into. clay-sessions must know
|
|
706
|
-
// so
|
|
707
|
-
//
|
|
731
|
+
// into. clay-sessions and clay-notes must know their caller for depth and
|
|
732
|
+
// ownership rules, so they are re-instantiated bound to that session; the
|
|
733
|
+
// static instances only serve descriptor listing and fail closed on calls.
|
|
708
734
|
function getLocalMcpServers(forSession) {
|
|
709
|
-
if (!mcpServers) return undefined;
|
|
710
735
|
var extWs = browserState._extensionWs;
|
|
711
736
|
var extConnected = !!(extWs && extWs.readyState === 1);
|
|
712
737
|
var emailAvailable = !!(_email && typeof _email.hasEmailCapability === "function" && _email.hasEmailCapability());
|
|
713
|
-
var keys = Object.keys(mcpServers);
|
|
738
|
+
var keys = Object.keys(mcpServers || {});
|
|
714
739
|
var filtered = {};
|
|
715
740
|
var hasAny = false;
|
|
716
741
|
for (var i = 0; i < keys.length; i++) {
|
|
@@ -726,6 +751,15 @@ function createProjectContext(opts) {
|
|
|
726
751
|
}
|
|
727
752
|
continue;
|
|
728
753
|
}
|
|
754
|
+
if (name === "clay-notes" && forSession) {
|
|
755
|
+
try {
|
|
756
|
+
var boundNotes = _sessionNotes.createMcpServer(adapter, forSession);
|
|
757
|
+
if (boundNotes) { filtered[name] = boundNotes; hasAny = true; }
|
|
758
|
+
} catch (e) {
|
|
759
|
+
console.error("[project] Failed to bind session notes MCP server:", e.message);
|
|
760
|
+
}
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
729
763
|
filtered[name] = mcpServers[name];
|
|
730
764
|
hasAny = true;
|
|
731
765
|
}
|
|
@@ -769,8 +803,15 @@ function createProjectContext(opts) {
|
|
|
769
803
|
}
|
|
770
804
|
return false;
|
|
771
805
|
},
|
|
772
|
-
getSessionSystemPrompt: function (session) {
|
|
773
|
-
|
|
806
|
+
getSessionSystemPrompt: function (session) {
|
|
807
|
+
return composeSystemPrompts([
|
|
808
|
+
_sessionPair.getSystemPrompt(session),
|
|
809
|
+
_sessionNotes.getSystemPrompt(session),
|
|
810
|
+
]);
|
|
811
|
+
},
|
|
812
|
+
getSessionToolDefs: function (session) {
|
|
813
|
+
return _sessionPair.getToolDefs(session).concat(_sessionNotes.getToolDefs(session));
|
|
814
|
+
},
|
|
774
815
|
});
|
|
775
816
|
|
|
776
817
|
// --- Loop engine (delegated to project-loop.js) ---
|
|
@@ -807,7 +848,6 @@ function createProjectContext(opts) {
|
|
|
807
848
|
|
|
808
849
|
// --- Terminal manager ---
|
|
809
850
|
var tm = createTerminalManager({ cwd: cwd, send: send, sendTo: sendTo });
|
|
810
|
-
var nm = createNotesManager({ cwd: cwd, send: send, sendTo: sendTo });
|
|
811
851
|
|
|
812
852
|
// Check for updates in background (admin only). The result is stored in
|
|
813
853
|
// latestVersion; broadcast is handled by the hourly scheduler below, so
|
|
@@ -1375,8 +1415,8 @@ function createProjectContext(opts) {
|
|
|
1375
1415
|
// --- MCP bridge handler for Codex and session-bound Kiro tools ---
|
|
1376
1416
|
// Provides list_tools and call_tool operations over HTTP for mcp-bridge-server.js.
|
|
1377
1417
|
// The normal Codex bridge excludes local MCP servers it manages natively;
|
|
1378
|
-
// Kiro's
|
|
1379
|
-
function getMcpBridgeHandler(sessionId,
|
|
1418
|
+
// Kiro's session-only bridge exposes only tools bound to its Clay session.
|
|
1419
|
+
function getMcpBridgeHandler(sessionId, sessionOnly) {
|
|
1380
1420
|
var boundSession = Number.isInteger(sessionId) ? sm.sessions.get(sessionId) : null;
|
|
1381
1421
|
// Build set of local MCP server names to exclude (Codex handles these natively)
|
|
1382
1422
|
var localMcpNames = {};
|
|
@@ -1393,7 +1433,19 @@ function createProjectContext(opts) {
|
|
|
1393
1433
|
listTools: function () {
|
|
1394
1434
|
var tools = [];
|
|
1395
1435
|
var toJSONSchema;
|
|
1396
|
-
|
|
1436
|
+
var zod;
|
|
1437
|
+
try { zod = require("zod"); toJSONSchema = zod.toJSONSchema; } catch (e) { /* fallback */ }
|
|
1438
|
+
|
|
1439
|
+
function normalizeToolSchema(inputSchema) {
|
|
1440
|
+
if (inputSchema && typeof inputSchema.type === "string") return inputSchema;
|
|
1441
|
+
try {
|
|
1442
|
+
if (toJSONSchema && inputSchema) {
|
|
1443
|
+
var schema = inputSchema.safeParse ? inputSchema : zod.object(inputSchema);
|
|
1444
|
+
return toJSONSchema(schema);
|
|
1445
|
+
}
|
|
1446
|
+
} catch (e) { /* fallback */ }
|
|
1447
|
+
return { type: "object", properties: {} };
|
|
1448
|
+
}
|
|
1397
1449
|
|
|
1398
1450
|
// Helper to extract tools from an SDK MCP server object
|
|
1399
1451
|
function extractServerTools(serverName, server) {
|
|
@@ -1401,10 +1453,7 @@ function createProjectContext(opts) {
|
|
|
1401
1453
|
var toolNames = Object.keys(server.instance._registeredTools);
|
|
1402
1454
|
for (var j = 0; j < toolNames.length; j++) {
|
|
1403
1455
|
var toolDef = server.instance._registeredTools[toolNames[j]];
|
|
1404
|
-
var inputSchema =
|
|
1405
|
-
try {
|
|
1406
|
-
if (toJSONSchema && toolDef.inputSchema) inputSchema = toJSONSchema(toolDef.inputSchema);
|
|
1407
|
-
} catch (e) { /* fallback */ }
|
|
1456
|
+
var inputSchema = normalizeToolSchema(toolDef.inputSchema);
|
|
1408
1457
|
tools.push({
|
|
1409
1458
|
server: serverName,
|
|
1410
1459
|
name: toolNames[j],
|
|
@@ -1421,12 +1470,21 @@ function createProjectContext(opts) {
|
|
|
1421
1470
|
server: "clay-sessions",
|
|
1422
1471
|
name: pairTools[pti].name,
|
|
1423
1472
|
description: pairTools[pti].description || pairTools[pti].name,
|
|
1424
|
-
inputSchema: pairTools[pti].inputSchema
|
|
1473
|
+
inputSchema: normalizeToolSchema(pairTools[pti].inputSchema),
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
var noteTools = _sessionNotes.getToolDefs(boundSession);
|
|
1477
|
+
for (var nti = 0; nti < noteTools.length; nti++) {
|
|
1478
|
+
tools.push({
|
|
1479
|
+
server: "clay-notes",
|
|
1480
|
+
name: noteTools[nti].name,
|
|
1481
|
+
description: noteTools[nti].description || noteTools[nti].name,
|
|
1482
|
+
inputSchema: normalizeToolSchema(noteTools[nti].inputSchema),
|
|
1425
1483
|
});
|
|
1426
1484
|
}
|
|
1427
1485
|
}
|
|
1428
1486
|
|
|
1429
|
-
if (
|
|
1487
|
+
if (sessionOnly) return Promise.resolve(tools);
|
|
1430
1488
|
|
|
1431
1489
|
// In-app MCP servers (debate, browser, email).
|
|
1432
1490
|
// Use getLocalMcpServers() so clay-browser is hidden unless the
|
|
@@ -1461,7 +1519,15 @@ function createProjectContext(opts) {
|
|
|
1461
1519
|
}
|
|
1462
1520
|
}
|
|
1463
1521
|
}
|
|
1464
|
-
if (
|
|
1522
|
+
if (boundSession && serverName === "clay-notes") {
|
|
1523
|
+
var noteTools = _sessionNotes.getToolDefs(boundSession);
|
|
1524
|
+
for (var nti = 0; nti < noteTools.length; nti++) {
|
|
1525
|
+
if (noteTools[nti].name === toolName && typeof noteTools[nti].handler === "function") {
|
|
1526
|
+
return Promise.resolve(noteTools[nti].handler(args || {}));
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
if (sessionOnly) return Promise.reject(new Error("Session tool not found: " + serverName + "/" + toolName));
|
|
1465
1531
|
// Try in-app servers first (gated by extension connectivity for clay-browser).
|
|
1466
1532
|
var localMcp = getLocalMcpServers();
|
|
1467
1533
|
if (localMcp && localMcp[serverName]) {
|
package/lib/public/app.js
CHANGED
|
@@ -42,7 +42,6 @@ import { initProfile, getProfileLang } from './modules/profile.js';
|
|
|
42
42
|
import { initUserSettings } from './modules/user-settings.js';
|
|
43
43
|
import { initToolPalettes } from './modules/tool-palette.js';
|
|
44
44
|
import { initProjectSwitcher } from './modules/project-switcher.js';
|
|
45
|
-
import { initBranchSwitcher } from './modules/branch-switcher.js';
|
|
46
45
|
import { initSplitView } from './modules/split-view.js';
|
|
47
46
|
import { initPaneBridge } from './modules/pane-bridge.js';
|
|
48
47
|
import { initAdmin, checkAdminAccess } from './modules/admin.js';
|
|
@@ -97,6 +96,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
97
96
|
var sendBtn = $("send-btn");
|
|
98
97
|
function getStatusDot() {
|
|
99
98
|
return document.querySelector("#icon-strip-projects .icon-strip-item.active .icon-strip-status") ||
|
|
99
|
+
document.querySelector("#icon-strip-projects .icon-strip-wt-item.active .icon-strip-status") ||
|
|
100
100
|
document.querySelector("#icon-strip-users .icon-strip-mate.active .icon-strip-status");
|
|
101
101
|
}
|
|
102
102
|
var headerTitleEl = $("header-title");
|
|
@@ -287,8 +287,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
287
287
|
projectName: projectName,
|
|
288
288
|
cwd: "",
|
|
289
289
|
currentSlug: currentSlug,
|
|
290
|
-
projects: [],
|
|
291
|
-
homeHubVisible: false,
|
|
292
290
|
currentProjectOwnerId: null,
|
|
293
291
|
isOsUsers: false,
|
|
294
292
|
skipPermsEnabled: false,
|
|
@@ -481,7 +479,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
481
479
|
// keydown listener registers later — capture-phase ordering doesn't
|
|
482
480
|
// matter here but it keeps related bootstrap steps adjacent.
|
|
483
481
|
initProjectSwitcher();
|
|
484
|
-
initBranchSwitcher();
|
|
485
482
|
initSplitView();
|
|
486
483
|
initPaneBridge();
|
|
487
484
|
|
|
@@ -223,6 +223,7 @@
|
|
|
223
223
|
|
|
224
224
|
/* Show dot only on active item or when processing */
|
|
225
225
|
.icon-strip-item.active .icon-strip-status,
|
|
226
|
+
.icon-strip-wt-item.active .icon-strip-status,
|
|
226
227
|
.icon-strip-status.processing {
|
|
227
228
|
opacity: 1;
|
|
228
229
|
}
|
|
@@ -247,6 +248,7 @@
|
|
|
247
248
|
|
|
248
249
|
/* Pending permission shake */
|
|
249
250
|
.icon-strip-item.has-pending-perm,
|
|
251
|
+
.icon-strip-wt-item.has-pending-perm,
|
|
250
252
|
.icon-strip-mate.has-pending-perm {
|
|
251
253
|
animation: permShake 2s ease-in-out infinite;
|
|
252
254
|
}
|
|
@@ -1144,6 +1146,155 @@
|
|
|
1144
1146
|
opacity: 1;
|
|
1145
1147
|
}
|
|
1146
1148
|
|
|
1149
|
+
/* --- Worktree folder groups --- */
|
|
1150
|
+
.icon-strip-group {
|
|
1151
|
+
display: flex;
|
|
1152
|
+
flex-direction: column;
|
|
1153
|
+
align-items: center;
|
|
1154
|
+
position: relative;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
.icon-strip-group .folder-header {
|
|
1158
|
+
position: relative;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
.icon-strip-group-chevron {
|
|
1162
|
+
position: absolute;
|
|
1163
|
+
bottom: 2px;
|
|
1164
|
+
right: 2px;
|
|
1165
|
+
width: 16px;
|
|
1166
|
+
height: 16px;
|
|
1167
|
+
border-radius: 50%;
|
|
1168
|
+
background: var(--bg-alt);
|
|
1169
|
+
border: 1px solid var(--border);
|
|
1170
|
+
display: flex;
|
|
1171
|
+
align-items: center;
|
|
1172
|
+
justify-content: center;
|
|
1173
|
+
color: var(--text-dimmer);
|
|
1174
|
+
pointer-events: auto;
|
|
1175
|
+
cursor: pointer;
|
|
1176
|
+
transition: transform 0.15s ease, background 0.15s, border-color 0.15s;
|
|
1177
|
+
z-index: 2;
|
|
1178
|
+
line-height: 1;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
.icon-strip-group-chevron i,
|
|
1182
|
+
.icon-strip-group-chevron svg {
|
|
1183
|
+
width: 10px;
|
|
1184
|
+
height: 10px;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
.icon-strip-group-chevron:hover {
|
|
1188
|
+
background: var(--bg-hover, var(--bg-alt));
|
|
1189
|
+
border-color: var(--text-dimmer);
|
|
1190
|
+
color: var(--text-secondary);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
.icon-strip-group.collapsed .icon-strip-group-chevron {
|
|
1194
|
+
background: var(--border);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
.icon-strip-group-items {
|
|
1198
|
+
display: flex;
|
|
1199
|
+
flex-direction: column;
|
|
1200
|
+
align-items: center;
|
|
1201
|
+
overflow: hidden;
|
|
1202
|
+
transition: max-height 0.2s ease;
|
|
1203
|
+
max-height: 500px;
|
|
1204
|
+
border-left: 2px solid var(--border);
|
|
1205
|
+
margin-left: 0;
|
|
1206
|
+
padding-left: 0;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
.icon-strip-group.collapsed .icon-strip-group-items {
|
|
1210
|
+
max-height: 0;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/* Worktree items (smaller than regular project icons) */
|
|
1214
|
+
.icon-strip-wt-item {
|
|
1215
|
+
width: 48px;
|
|
1216
|
+
height: 40px;
|
|
1217
|
+
display: flex;
|
|
1218
|
+
align-items: center;
|
|
1219
|
+
justify-content: center;
|
|
1220
|
+
cursor: pointer;
|
|
1221
|
+
position: relative;
|
|
1222
|
+
text-decoration: none;
|
|
1223
|
+
color: var(--text-secondary);
|
|
1224
|
+
transition: background 0.15s;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
.icon-strip-wt-item::before {
|
|
1228
|
+
content: "";
|
|
1229
|
+
position: absolute;
|
|
1230
|
+
width: 30px;
|
|
1231
|
+
height: 30px;
|
|
1232
|
+
border-radius: 8px;
|
|
1233
|
+
background: var(--bg-alt);
|
|
1234
|
+
opacity: 0.7;
|
|
1235
|
+
transition: opacity 0.15s, background 0.15s;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
.icon-strip-wt-item:hover::before {
|
|
1239
|
+
opacity: 1;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
.icon-strip-wt-item.active::before {
|
|
1243
|
+
background: var(--accent);
|
|
1244
|
+
opacity: 1;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
.icon-strip-wt-item.active {
|
|
1248
|
+
color: #fff;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
.icon-strip-wt-item .wt-branch-abbrev {
|
|
1252
|
+
font-size: 11px;
|
|
1253
|
+
font-weight: 600;
|
|
1254
|
+
letter-spacing: -0.5px;
|
|
1255
|
+
pointer-events: none;
|
|
1256
|
+
position: relative;
|
|
1257
|
+
z-index: 1;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
/* Disabled/inaccessible worktree */
|
|
1261
|
+
.icon-strip-wt-item.wt-disabled {
|
|
1262
|
+
opacity: 0.35;
|
|
1263
|
+
cursor: not-allowed;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
.icon-strip-wt-item.wt-disabled::after {
|
|
1267
|
+
content: "\1F6AB";
|
|
1268
|
+
position: absolute;
|
|
1269
|
+
font-size: 14px;
|
|
1270
|
+
z-index: 3;
|
|
1271
|
+
filter: grayscale(0.3);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
/* Group add button */
|
|
1275
|
+
.icon-strip-group-add {
|
|
1276
|
+
width: 30px;
|
|
1277
|
+
height: 30px;
|
|
1278
|
+
border-radius: 8px;
|
|
1279
|
+
border: 1.5px dashed var(--border);
|
|
1280
|
+
background: transparent;
|
|
1281
|
+
color: var(--text-dimmer);
|
|
1282
|
+
font-size: 16px;
|
|
1283
|
+
font-weight: 400;
|
|
1284
|
+
cursor: pointer;
|
|
1285
|
+
display: flex;
|
|
1286
|
+
align-items: center;
|
|
1287
|
+
justify-content: center;
|
|
1288
|
+
margin: 2px 0 4px 0;
|
|
1289
|
+
transition: border-color 0.15s, color 0.15s, background 0.15s;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
.icon-strip-group-add:hover {
|
|
1293
|
+
border-color: var(--accent);
|
|
1294
|
+
color: var(--accent);
|
|
1295
|
+
background: rgba(var(--accent-rgb, 124, 58, 237), 0.08);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1147
1298
|
/* --- Worktree creation modal --- */
|
|
1148
1299
|
.wt-modal-overlay {
|
|
1149
1300
|
position: fixed;
|
|
@@ -1258,6 +1409,45 @@ select.wt-modal-input {
|
|
|
1258
1409
|
cursor: not-allowed;
|
|
1259
1410
|
}
|
|
1260
1411
|
|
|
1412
|
+
/* --- Mobile worktree folder --- */
|
|
1413
|
+
.mobile-project-folder {
|
|
1414
|
+
display: flex;
|
|
1415
|
+
flex-direction: column;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
.mobile-project-item.wt-item {
|
|
1419
|
+
padding-left: 28px;
|
|
1420
|
+
opacity: 0.85;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
.mobile-project-item.wt-disabled {
|
|
1424
|
+
opacity: 0.35;
|
|
1425
|
+
cursor: not-allowed;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
.mobile-folder-chevron {
|
|
1429
|
+
font-size: 8px;
|
|
1430
|
+
color: var(--text-dimmer);
|
|
1431
|
+
margin-left: auto;
|
|
1432
|
+
cursor: pointer;
|
|
1433
|
+
transition: transform 0.15s ease;
|
|
1434
|
+
padding: 4px;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
.mobile-project-folder.collapsed .mobile-folder-chevron {
|
|
1438
|
+
transform: rotate(-90deg);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
.mobile-folder-items {
|
|
1442
|
+
overflow: hidden;
|
|
1443
|
+
transition: max-height 0.2s ease;
|
|
1444
|
+
max-height: 500px;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
.mobile-project-folder.collapsed .mobile-folder-items {
|
|
1448
|
+
max-height: 0;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1261
1451
|
/* --- Per-project presence avatars --- */
|
|
1262
1452
|
/* --- Mobile: hide icon strip --- */
|
|
1263
1453
|
/* --- Skeleton loading placeholders --- */
|