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.
Files changed (35) hide show
  1. package/lib/mates-prompts.js +3 -2
  2. package/lib/notes.js +6 -0
  3. package/lib/project-http.js +3 -6
  4. package/lib/project-session-notes.js +217 -0
  5. package/lib/project.js +84 -18
  6. package/lib/public/app.js +1 -4
  7. package/lib/public/css/icon-strip.css +190 -0
  8. package/lib/public/css/messages.css +104 -2
  9. package/lib/public/css/mobile-nav.css +0 -4
  10. package/lib/public/css/pane.css +27 -0
  11. package/lib/public/css/rewind.css +10 -0
  12. package/lib/public/css/sticky-notes.css +43 -0
  13. package/lib/public/css/title-bar.css +0 -169
  14. package/lib/public/index.html +0 -6
  15. package/lib/public/modules/app-favicon.js +10 -1
  16. package/lib/public/modules/app-home-hub.js +0 -2
  17. package/lib/public/modules/app-messages.js +18 -6
  18. package/lib/public/modules/app-projects.js +30 -43
  19. package/lib/public/modules/app-rendering.js +42 -3
  20. package/lib/public/modules/dom-refs.js +1 -0
  21. package/lib/public/modules/input.js +13 -0
  22. package/lib/public/modules/project-switcher.js +1 -2
  23. package/lib/public/modules/sidebar-mobile.js +6 -19
  24. package/lib/public/modules/sidebar-projects.js +232 -63
  25. package/lib/public/modules/split-pair-ui.js +30 -0
  26. package/lib/public/modules/sticky-note-markdown.js +89 -0
  27. package/lib/public/modules/sticky-notes.js +56 -103
  28. package/lib/sdk-bridge.js +25 -1
  29. package/lib/session-notes-mcp-server.js +39 -0
  30. package/lib/ws-schema.js +1 -0
  31. package/lib/yoke/adapters/codex.js +44 -8
  32. package/lib/yoke/mcp-bridge-server.js +7 -7
  33. package/package.json +1 -1
  34. package/lib/public/modules/branch-switcher.js +0 -178
  35. package/lib/public/modules/worktree-family.js +0 -60
@@ -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 contains sticky notes left by the user. " +
49
- "Read this file when starting a conversation for important context. " +
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;
@@ -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, isGitRepo: true }));
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", isGitRepo: false }));
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.pairOnly === true);
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 its caller (depth guard + child ownership),
706
- // so it is re-instantiated bound to that session; the static instance in
707
- // mcpServers only serves descriptor listing and fails closed on calls.
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) { return _sessionPair.getSystemPrompt(session); },
773
- getSessionToolDefs: function (session) { return _sessionPair.getToolDefs(session); },
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 pair-only bridge exposes only the tools bound to its Clay session.
1379
- function getMcpBridgeHandler(sessionId, pairOnly) {
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
- try { toJSONSchema = require("zod").toJSONSchema; } catch (e) { /* fallback */ }
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 = { type: "object", properties: {} };
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 || { type: "object", properties: {} },
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 (pairOnly) return Promise.resolve(tools);
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 (pairOnly) return Promise.reject(new Error("Pair tool not found: " + toolName));
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