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
package/lib/mates-prompts.js
CHANGED
|
@@ -45,8 +45,9 @@ var STICKY_NOTES_SECTION =
|
|
|
45
45
|
"\n\n" + STICKY_NOTES_MARKER + "\n" +
|
|
46
46
|
"## Sticky Notes\n\n" +
|
|
47
47
|
"**This section is managed by the system and cannot be removed.**\n\n" +
|
|
48
|
-
"Your `knowledge/sticky-notes.md` file
|
|
49
|
-
"
|
|
48
|
+
"Your `knowledge/sticky-notes.md` file is shared project memory that persists across sessions. " +
|
|
49
|
+
"It may contain checklists, to-do items, work goals, handoffs, unfinished work, durable decisions and constraints, or project knowledge that future sessions should remember. " +
|
|
50
|
+
"Read this file when starting a conversation and use relevant notes to recover the current context and next actions. " +
|
|
50
51
|
"These notes are read-only. You cannot create, update, or delete them.\n";
|
|
51
52
|
|
|
52
53
|
var PROJECT_REGISTRY_MARKER = "<!-- PROJECT_REGISTRY_MANAGED_BY_SYSTEM -->";
|
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 = 20000;
|
|
4
|
+
var MAX_ACTIVE_NOTES = 20;
|
|
5
|
+
var MAX_PROMPT_CHARS = 4000;
|
|
6
|
+
var MAX_NOTE_PROMPT_CHARS = 800;
|
|
7
|
+
var NOTES_LABEL = "--- Project sticky notes (cross-session work memory; manage via clay-notes tools) ---";
|
|
8
|
+
var NOTES_HINT = "- More notes are available; call list_notes for the rest.";
|
|
9
|
+
var PROACTIVE_POLICY = "The board persists across sessions and is shared with the user and every Clay agent working on this project. People may use it freely. As a Clay agent, use it specifically as cross-session work memory: checklists and to-do lists, work goals, handoffs, unfinished work, durable decisions and constraints, and knowledge that should remain available after this session ends. Create or update a note proactively when the user asks for a handoff, establishes something future sessions must remember, or when preserving the current goal and next actions would make continuation easier. Put a concise plain-text title on the first line. Add a detailed body with the relevant background, rationale, decisions, completed work, current state, validation, next steps, blockers, and references. If an injected preview is relevant or truncated, call list_notes and read the full note before acting. Do not use notes as a transcript, routine progress log, or self-announcement. Keep one coherent topic per note and consolidate or remove stale notes instead of creating duplicates.";
|
|
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 " + MAX_NOTE_TEXT_CHARS + " 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
|
|