clay-server 2.26.0-beta.17 → 2.26.0-beta.19
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/browser-mcp-server.js +4 -4
- package/lib/debate-mcp-server.js +94 -0
- package/lib/mates.js +12 -24
- package/lib/project-debate.js +64 -81
- package/lib/project-mate-interaction.js +4 -5
- package/lib/project.js +92 -37
- package/lib/public/app.js +29 -3
- package/lib/public/css/filebrowser.css +41 -4
- package/lib/public/css/rewind.css +17 -4
- package/lib/public/index.html +1 -0
- package/lib/public/modules/debate.js +116 -2
- package/lib/public/modules/terminal.js +62 -6
- package/lib/sdk-bridge.js +37 -16
- package/package.json +1 -1
|
@@ -111,7 +111,7 @@ function create(sendCommand, getTabList, contextOps) {
|
|
|
111
111
|
// --- browser_screenshot ---
|
|
112
112
|
tools.push(tool(
|
|
113
113
|
"browser_screenshot",
|
|
114
|
-
"Capture a screenshot of a browser tab
|
|
114
|
+
"Capture a screenshot of a browser tab. Skip if the tab is already attached as a context source (data is auto-injected).",
|
|
115
115
|
buildShape({
|
|
116
116
|
tabId: { type: "number", description: "Tab ID" },
|
|
117
117
|
selector: { type: "string", description: "CSS selector to capture a specific element (optional)" },
|
|
@@ -134,7 +134,7 @@ function create(sendCommand, getTabList, contextOps) {
|
|
|
134
134
|
// --- browser_console ---
|
|
135
135
|
tools.push(tool(
|
|
136
136
|
"browser_console",
|
|
137
|
-
"Read captured console logs from a tab (
|
|
137
|
+
"Read captured console logs from a tab. Skip if the tab is already a context source (data is auto-injected).",
|
|
138
138
|
buildShape({
|
|
139
139
|
tabId: { type: "number", description: "Tab ID" },
|
|
140
140
|
}, ["tabId"]),
|
|
@@ -157,7 +157,7 @@ function create(sendCommand, getTabList, contextOps) {
|
|
|
157
157
|
// --- browser_network ---
|
|
158
158
|
tools.push(tool(
|
|
159
159
|
"browser_network",
|
|
160
|
-
"Read captured network requests (fetch/XHR) from a tab",
|
|
160
|
+
"Read captured network requests (fetch/XHR) from a tab. Skip if the tab is already a context source.",
|
|
161
161
|
buildShape({
|
|
162
162
|
tabId: { type: "number", description: "Tab ID" },
|
|
163
163
|
}, ["tabId"]),
|
|
@@ -181,7 +181,7 @@ function create(sendCommand, getTabList, contextOps) {
|
|
|
181
181
|
// --- browser_read_page ---
|
|
182
182
|
tools.push(tool(
|
|
183
183
|
"browser_read_page",
|
|
184
|
-
"Read page text content (innerText).
|
|
184
|
+
"Read page text content (innerText). Skip if the tab is already a context source (text is auto-injected). Use for tabs NOT in context sources, or to read a specific element via selector.",
|
|
185
185
|
buildShape({
|
|
186
186
|
tabId: { type: "number", description: "Tab ID" },
|
|
187
187
|
selector: { type: "string", description: "CSS selector to read specific element (optional)" },
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Debate MCP Server for Clay (in-process SDK version)
|
|
2
|
+
// Provides the propose_debate tool so mates can propose debates
|
|
3
|
+
// via the SDK tool system instead of writing files to disk.
|
|
4
|
+
//
|
|
5
|
+
// Usage:
|
|
6
|
+
// var debateMcp = require("./debate-mcp-server");
|
|
7
|
+
// var mcpConfig = debateMcp.create(onPropose);
|
|
8
|
+
// // Pass mcpConfig to sdk-bridge opts.mcpServers
|
|
9
|
+
|
|
10
|
+
var z;
|
|
11
|
+
try { z = require("zod"); } catch (e) { z = null; }
|
|
12
|
+
|
|
13
|
+
function buildShape(props, required) {
|
|
14
|
+
if (!z) return {};
|
|
15
|
+
var shape = {};
|
|
16
|
+
var keys = Object.keys(props);
|
|
17
|
+
for (var i = 0; i < keys.length; i++) {
|
|
18
|
+
var k = keys[i];
|
|
19
|
+
var p = props[k];
|
|
20
|
+
var field;
|
|
21
|
+
if (p.type === "number") field = z.number();
|
|
22
|
+
else if (p.type === "boolean") field = z.boolean();
|
|
23
|
+
else if (p.enum) field = z.enum(p.enum);
|
|
24
|
+
else field = z.string();
|
|
25
|
+
if (p.description) field = field.describe(p.description);
|
|
26
|
+
if (!required || required.indexOf(k) === -1) field = field.optional();
|
|
27
|
+
shape[k] = field;
|
|
28
|
+
}
|
|
29
|
+
return shape;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// onPropose(briefData) -> Promise<{action: "start"|"cancel"}>
|
|
33
|
+
// The returned Promise blocks the tool until the user approves or cancels.
|
|
34
|
+
function create(onPropose) {
|
|
35
|
+
var sdk;
|
|
36
|
+
try { sdk = require("@anthropic-ai/claude-agent-sdk"); } catch (e) {
|
|
37
|
+
console.error("[debate-mcp] Failed to load SDK:", e.message);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
var createSdkMcpServer = sdk.createSdkMcpServer;
|
|
42
|
+
var tool = sdk.tool;
|
|
43
|
+
if (!createSdkMcpServer || !tool) {
|
|
44
|
+
console.error("[debate-mcp] SDK missing createSdkMcpServer or tool helper");
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
var tools = [];
|
|
49
|
+
|
|
50
|
+
tools.push(tool(
|
|
51
|
+
"propose_debate",
|
|
52
|
+
"Propose a structured debate among Clay Mates. The user will see an inline approval card. The tool blocks until the user approves or cancels.",
|
|
53
|
+
buildShape({
|
|
54
|
+
topic: { type: "string", description: "The debate topic" },
|
|
55
|
+
format: { type: "string", description: "Debate format, e.g. free_discussion (default)" },
|
|
56
|
+
context: { type: "string", description: "Key context from the conversation that panelists should know" },
|
|
57
|
+
specialRequests: { type: "string", description: "Special instructions for the debate, or empty" },
|
|
58
|
+
panelists: { type: "string", description: "JSON array of panelist objects: [{\"mateId\": \"<UUID>\", \"role\": \"perspective\", \"brief\": \"guidance\"}]" },
|
|
59
|
+
}, ["topic", "panelists"]),
|
|
60
|
+
function (args) {
|
|
61
|
+
var panelists;
|
|
62
|
+
try {
|
|
63
|
+
panelists = JSON.parse(args.panelists);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
return Promise.resolve({
|
|
66
|
+
content: [{ type: "text", text: "Error: panelists must be a valid JSON array. Got: " + (args.panelists || "").substring(0, 100) }],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
var briefData = {
|
|
71
|
+
topic: args.topic || "Untitled debate",
|
|
72
|
+
format: args.format || "free_discussion",
|
|
73
|
+
context: args.context || "",
|
|
74
|
+
specialRequests: args.specialRequests || null,
|
|
75
|
+
panelists: panelists,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return onPropose(briefData).then(function (result) {
|
|
79
|
+
if (result && result.action === "start") {
|
|
80
|
+
return { content: [{ type: "text", text: "Debate approved and started. Topic: " + briefData.topic }] };
|
|
81
|
+
}
|
|
82
|
+
return { content: [{ type: "text", text: "Debate proposal was cancelled by the user." }] };
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
));
|
|
86
|
+
|
|
87
|
+
return createSdkMcpServer({
|
|
88
|
+
name: "clay-debate",
|
|
89
|
+
version: "1.0.0",
|
|
90
|
+
tools: tools,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { create: create };
|
package/lib/mates.js
CHANGED
|
@@ -633,35 +633,23 @@ var DEBATE_AWARENESS_SECTION =
|
|
|
633
633
|
"\n\n" + DEBATE_AWARENESS_MARKER + "\n" +
|
|
634
634
|
"## Proposing Debates\n\n" +
|
|
635
635
|
"**This section is managed by the system and cannot be removed.**\n\n" +
|
|
636
|
-
"When the user suggests
|
|
637
|
-
"
|
|
638
|
-
"
|
|
636
|
+
"When the user suggests a debate, you MUST use the `propose_debate` tool. " +
|
|
637
|
+
"NEVER write debate files to disk. NEVER mkdir for debates. NEVER use Write/Bash for debate setup. " +
|
|
638
|
+
"The ONLY way to propose a debate is the `propose_debate` tool.\n\n" +
|
|
639
639
|
"**How to propose a debate:**\n" +
|
|
640
|
-
"
|
|
641
|
-
"
|
|
642
|
-
"
|
|
643
|
-
"
|
|
644
|
-
"
|
|
645
|
-
"
|
|
646
|
-
"{\n" +
|
|
647
|
-
"
|
|
648
|
-
" \"format\": \"free_discussion\",\n" +
|
|
649
|
-
" \"context\": \"Key context from the conversation that panelists should know\",\n" +
|
|
650
|
-
" \"specialRequests\": \"Any special instructions, or null\",\n" +
|
|
651
|
-
" \"panelists\": [\n" +
|
|
652
|
-
" {\n" +
|
|
653
|
-
" \"mateId\": \"<mate UUID from the team roster above>\",\n" +
|
|
654
|
-
" \"role\": \"The perspective or stance this panelist should take\",\n" +
|
|
655
|
-
" \"brief\": \"Specific guidance for this panelist\"\n" +
|
|
656
|
-
" }\n" +
|
|
657
|
-
" ]\n" +
|
|
658
|
-
"}\n" +
|
|
659
|
-
"```\n\n" +
|
|
640
|
+
"Call the `propose_debate` tool with these parameters:\n" +
|
|
641
|
+
"- `topic` (required): The refined debate topic\n" +
|
|
642
|
+
"- `format`: Debate format, default \"free_discussion\"\n" +
|
|
643
|
+
"- `context`: Key context from the conversation that panelists should know\n" +
|
|
644
|
+
"- `specialRequests`: Any special instructions\n" +
|
|
645
|
+
"- `panelists` (required): A JSON string array of panelist objects:\n" +
|
|
646
|
+
" `[{\"mateId\": \"<mate UUID from team roster>\", \"role\": \"perspective\", \"brief\": \"guidance\"}]`\n\n" +
|
|
647
|
+
"The user will see an inline approval card. The tool blocks until they approve or cancel.\n\n" +
|
|
660
648
|
"**Rules:**\n" +
|
|
661
649
|
"- Choose 2-4 panelists from the team roster. Pick mates whose expertise fits the topic.\n" +
|
|
662
650
|
"- Do NOT include yourself as a panelist. You will moderate the debate.\n" +
|
|
663
651
|
"- Only propose a debate when the user explicitly asks for one.\n" +
|
|
664
|
-
"-
|
|
652
|
+
"- Do NOT write files to disk for debate proposals. Always use the propose_debate tool.\n";
|
|
665
653
|
|
|
666
654
|
function enforceDebateAwareness(filePath) {
|
|
667
655
|
if (!fs.existsSync(filePath)) return false;
|
package/lib/project-debate.js
CHANGED
|
@@ -14,6 +14,13 @@ var matesModule = require("./mates");
|
|
|
14
14
|
*/
|
|
15
15
|
function attachDebate(ctx) {
|
|
16
16
|
|
|
17
|
+
// For mate projects, enforce latest debate awareness prompt in CLAUDE.md
|
|
18
|
+
// so mates use the propose_debate MCP tool instead of writing files.
|
|
19
|
+
if (ctx.isMate) {
|
|
20
|
+
var _debateClaudeMdPath = path.join(ctx.cwd, "CLAUDE.md");
|
|
21
|
+
try { matesModule.enforceDebateAwareness(_debateClaudeMdPath); } catch (e) {}
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
// --- Helpers shared with other modules ---
|
|
18
25
|
|
|
19
26
|
function escapeRegex(str) {
|
|
@@ -270,9 +277,9 @@ function attachDebate(ctx) {
|
|
|
270
277
|
var mateCtx = debate.mateCtx || matesModule.buildMateCtx(null);
|
|
271
278
|
debate.nameMap = buildDebateNameMap(debate.panelists, mateCtx);
|
|
272
279
|
|
|
273
|
-
//
|
|
274
|
-
if (!debate.setupSessionId) {
|
|
275
|
-
console.log("[debate] Brief picked up
|
|
280
|
+
// quickStart from DM or no setupSessionId: show brief card for user approval
|
|
281
|
+
if (!debate.setupSessionId || debate.quickStart) {
|
|
282
|
+
console.log("[debate] Brief picked up, entering review phase. Topic:", debate.topic);
|
|
276
283
|
debate.phase = "reviewing";
|
|
277
284
|
persistDebateState(session);
|
|
278
285
|
|
|
@@ -323,85 +330,18 @@ function attachDebate(ctx) {
|
|
|
323
330
|
// --- Restore debate on reconnect ---
|
|
324
331
|
|
|
325
332
|
function restoreDebateState(ws) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
333
|
+
// On server restart, SDK sessions are lost so debates cannot continue.
|
|
334
|
+
// Clear stale debate state instead of restoring dead UI.
|
|
329
335
|
ctx.sm.sessions.forEach(function (session) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
// Restore _debate from persisted state (pass userId for correct mateCtx)
|
|
340
|
-
var debate = restoreDebateFromState(session, userId);
|
|
341
|
-
if (!debate) return;
|
|
342
|
-
|
|
343
|
-
// Update mateCtx with the connected user's context
|
|
344
|
-
debate.mateCtx = mateCtx;
|
|
345
|
-
debate.nameMap = buildDebateNameMap(debate.panelists, mateCtx);
|
|
346
|
-
|
|
347
|
-
var moderatorProfile = ctx.getMateProfile(mateCtx, debate.moderatorId);
|
|
348
|
-
|
|
349
|
-
if (phase === "preparing") {
|
|
350
|
-
var briefPath = debate.briefPath;
|
|
351
|
-
if (!briefPath && debate.debateId) {
|
|
352
|
-
briefPath = path.join(ctx.cwd, ".clay", "debates", debate.debateId, "brief.json");
|
|
353
|
-
}
|
|
354
|
-
if (!briefPath) return;
|
|
355
|
-
|
|
356
|
-
console.log("[debate] Restoring debate (preparing). topic:", debate.topic, "briefPath:", briefPath);
|
|
357
|
-
startDebateBriefWatcher(session, debate, briefPath);
|
|
358
|
-
|
|
359
|
-
ctx.sendTo(ws, {
|
|
360
|
-
type: "debate_preparing",
|
|
361
|
-
topic: debate.topic,
|
|
362
|
-
moderatorId: debate.moderatorId,
|
|
363
|
-
moderatorName: moderatorProfile.name,
|
|
364
|
-
setupSessionId: debate.setupSessionId,
|
|
365
|
-
panelists: debate.panelists.map(function (p) {
|
|
366
|
-
var prof = ctx.getMateProfile(mateCtx, p.mateId);
|
|
367
|
-
return { mateId: p.mateId, name: prof.name };
|
|
368
|
-
}),
|
|
369
|
-
});
|
|
370
|
-
} else if (phase === "reviewing") {
|
|
371
|
-
console.log("[debate] Restoring debate (reviewing). topic:", debate.topic);
|
|
372
|
-
ctx.sendTo(ws, {
|
|
373
|
-
type: "debate_brief_ready",
|
|
374
|
-
debateId: debate.debateId,
|
|
375
|
-
topic: debate.topic,
|
|
376
|
-
format: debate.format || "free_discussion",
|
|
377
|
-
context: debate.context || "",
|
|
378
|
-
specialRequests: debate.specialRequests || null,
|
|
379
|
-
moderatorId: debate.moderatorId,
|
|
380
|
-
moderatorName: moderatorProfile.name,
|
|
381
|
-
panelists: debate.panelists.map(function (p) {
|
|
382
|
-
var prof = ctx.getMateProfile(mateCtx, p.mateId);
|
|
383
|
-
return { mateId: p.mateId, name: prof.name, role: p.role || "", brief: p.brief || "" };
|
|
384
|
-
}),
|
|
385
|
-
});
|
|
386
|
-
} else if (phase === "live") {
|
|
387
|
-
console.log("[debate] Restoring debate (live). topic:", debate.topic, "awaitingConclude:", debate.awaitingConcludeConfirm);
|
|
388
|
-
// Debate was live when server restarted. It can't resume AI turns,
|
|
389
|
-
// but we can show the sticky and let user see history.
|
|
390
|
-
ctx.sendTo(ws, {
|
|
391
|
-
type: "debate_started",
|
|
392
|
-
topic: debate.topic,
|
|
393
|
-
format: debate.format,
|
|
394
|
-
round: debate.round,
|
|
395
|
-
moderatorId: debate.moderatorId,
|
|
396
|
-
moderatorName: moderatorProfile.name,
|
|
397
|
-
panelists: debate.panelists.map(function (p) {
|
|
398
|
-
var prof = ctx.getMateProfile(mateCtx, p.mateId);
|
|
399
|
-
return { mateId: p.mateId, name: prof.name, role: p.role, avatarColor: prof.avatarColor, avatarStyle: prof.avatarStyle, avatarSeed: prof.avatarSeed };
|
|
400
|
-
}),
|
|
401
|
-
});
|
|
402
|
-
// If moderator had concluded, re-send conclude confirm so client shows End/Continue UI
|
|
403
|
-
if (debate.awaitingConcludeConfirm) {
|
|
404
|
-
ctx.sendTo(ws, { type: "debate_conclude_confirm", topic: debate.topic, round: debate.round });
|
|
336
|
+
if (session._debate) {
|
|
337
|
+
delete session._debate;
|
|
338
|
+
}
|
|
339
|
+
if (session.debateState) {
|
|
340
|
+
var phase = session.debateState.phase;
|
|
341
|
+
if (phase === "preparing" || phase === "reviewing" || phase === "live") {
|
|
342
|
+
console.log("[debate] Clearing stale debate state:", session.debateState.topic);
|
|
343
|
+
session.debateState = null;
|
|
344
|
+
ctx.sm.saveSessionFile(session);
|
|
405
345
|
}
|
|
406
346
|
}
|
|
407
347
|
});
|
|
@@ -1741,6 +1681,48 @@ function attachDebate(ctx) {
|
|
|
1741
1681
|
})();
|
|
1742
1682
|
}
|
|
1743
1683
|
|
|
1684
|
+
// --- MCP-based debate proposal approval ---
|
|
1685
|
+
|
|
1686
|
+
function handleMcpDebateApproval(session, briefData, mateId, ws) {
|
|
1687
|
+
if (session._debate && (session._debate.phase === "preparing" || session._debate.phase === "reviewing" || session._debate.phase === "live")) {
|
|
1688
|
+
console.warn("[debate] Cannot start MCP debate: another debate is active on this session");
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
var userId = ws && ws._clayUser ? ws._clayUser.id : (session.ownerId || ctx.projectOwnerId || null);
|
|
1693
|
+
var mateCtx = matesModule.buildMateCtx(userId);
|
|
1694
|
+
var debateId = "debate_" + Date.now();
|
|
1695
|
+
|
|
1696
|
+
var debate = {
|
|
1697
|
+
phase: "reviewing",
|
|
1698
|
+
topic: briefData.topic || "Untitled debate",
|
|
1699
|
+
format: briefData.format || "free_discussion",
|
|
1700
|
+
context: briefData.context || "",
|
|
1701
|
+
specialRequests: briefData.specialRequests || null,
|
|
1702
|
+
moderatorId: mateId,
|
|
1703
|
+
panelists: (briefData.panelists || []).map(function (p) {
|
|
1704
|
+
return { mateId: p.mateId, role: p.role || "", brief: p.brief || "" };
|
|
1705
|
+
}),
|
|
1706
|
+
mateCtx: mateCtx,
|
|
1707
|
+
moderatorSession: null,
|
|
1708
|
+
panelistSessions: {},
|
|
1709
|
+
nameMap: null,
|
|
1710
|
+
turnInProgress: false,
|
|
1711
|
+
pendingComment: null,
|
|
1712
|
+
round: 1,
|
|
1713
|
+
history: [],
|
|
1714
|
+
setupSessionId: null,
|
|
1715
|
+
debateId: debateId,
|
|
1716
|
+
briefPath: null,
|
|
1717
|
+
ownerId: userId,
|
|
1718
|
+
};
|
|
1719
|
+
debate.nameMap = buildDebateNameMap(debate.panelists, mateCtx);
|
|
1720
|
+
session._debate = debate;
|
|
1721
|
+
|
|
1722
|
+
console.log("[debate] MCP debate approved. Topic:", debate.topic, "debateId:", debateId);
|
|
1723
|
+
startDebateLive(session);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1744
1726
|
// --- Public API ---
|
|
1745
1727
|
|
|
1746
1728
|
return {
|
|
@@ -1753,6 +1735,7 @@ function attachDebate(ctx) {
|
|
|
1753
1735
|
handleDebateUserFloorResponse: handleDebateUserFloorResponse,
|
|
1754
1736
|
restoreDebateState: restoreDebateState,
|
|
1755
1737
|
checkForDmDebateBrief: checkForDmDebateBrief,
|
|
1738
|
+
handleMcpDebateApproval: handleMcpDebateApproval,
|
|
1756
1739
|
};
|
|
1757
1740
|
}
|
|
1758
1741
|
|
|
@@ -569,11 +569,10 @@ function attachMateInteraction(ctx) {
|
|
|
569
569
|
onDone: mentionCallbacks.onDone,
|
|
570
570
|
onError: mentionCallbacks.onError,
|
|
571
571
|
canUseTool: function (toolName, input, toolOpts) {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
// Route through the project session's permission system
|
|
572
|
+
// Use the shared whitelist from sdk-bridge (read-only tools + safe bash commands)
|
|
573
|
+
var whitelisted = sdk.checkToolWhitelist(toolName, input);
|
|
574
|
+
if (whitelisted) return Promise.resolve(whitelisted);
|
|
575
|
+
// Not whitelisted: route through the project session's permission system
|
|
577
576
|
return new Promise(function (resolve) {
|
|
578
577
|
var requestId = crypto.randomUUID();
|
|
579
578
|
session.pendingPermissions[requestId] = {
|
package/lib/project.js
CHANGED
|
@@ -293,6 +293,7 @@ function createProjectContext(opts) {
|
|
|
293
293
|
|
|
294
294
|
// --- Browser extension state ---
|
|
295
295
|
var _browserTabList = {}; // tabId -> { id, url, title, favIconUrl }
|
|
296
|
+
var _pendingDebateProposals = {}; // proposalId -> { resolve, briefData }
|
|
296
297
|
var _extensionWs = null; // WebSocket of the client with the Chrome extension
|
|
297
298
|
var _extToken = crypto.randomUUID(); // Auth token for MCP server bridge
|
|
298
299
|
var pendingExtensionRequests = {}; // requestId -> { resolve, timer }
|
|
@@ -557,47 +558,72 @@ function createProjectContext(opts) {
|
|
|
557
558
|
mateDisplayName: opts.mateDisplayName || "",
|
|
558
559
|
isMate: isMate,
|
|
559
560
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
560
|
-
mcpServers:
|
|
561
|
+
mcpServers: (function () {
|
|
562
|
+
var servers = {};
|
|
563
|
+
|
|
564
|
+
// Debate MCP server (available to both mates and main project)
|
|
561
565
|
try {
|
|
562
|
-
var
|
|
563
|
-
var
|
|
564
|
-
return
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
}
|
|
575
|
-
return active;
|
|
576
|
-
},
|
|
577
|
-
unwatchTab: function (tabId) {
|
|
578
|
-
var key = "tab:" + tabId;
|
|
579
|
-
var active = loadContextSources(slug);
|
|
580
|
-
var idx = active.indexOf(key);
|
|
581
|
-
if (idx !== -1) {
|
|
582
|
-
active.splice(idx, 1);
|
|
583
|
-
saveContextSources(slug, active);
|
|
584
|
-
var msg = JSON.stringify({ type: "context_sources_state", active: active });
|
|
585
|
-
for (var c of clients) { if (c.readyState === 1) c.send(msg); }
|
|
586
|
-
}
|
|
587
|
-
return active;
|
|
588
|
-
},
|
|
566
|
+
var debateMcp = require("./debate-mcp-server");
|
|
567
|
+
var debateMcpConfig = debateMcp.create(function onPropose(briefData) {
|
|
568
|
+
return new Promise(function (resolve) {
|
|
569
|
+
var proposalId = "dp_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
|
|
570
|
+
briefData.proposalId = proposalId;
|
|
571
|
+
_pendingDebateProposals[proposalId] = {
|
|
572
|
+
resolve: resolve,
|
|
573
|
+
briefData: briefData,
|
|
574
|
+
};
|
|
575
|
+
// The SDK sends tool_executing with briefData as input.
|
|
576
|
+
// Client renders the debate brief card when it sees propose_debate.
|
|
577
|
+
});
|
|
589
578
|
});
|
|
590
|
-
if (
|
|
591
|
-
var servers = {};
|
|
592
|
-
servers[mcpConfig.name || "clay-browser"] = mcpConfig;
|
|
593
|
-
return servers;
|
|
579
|
+
if (debateMcpConfig) servers[debateMcpConfig.name || "clay-debate"] = debateMcpConfig;
|
|
594
580
|
} catch (e) {
|
|
595
|
-
console.error("[project] Failed to create
|
|
596
|
-
return undefined;
|
|
581
|
+
console.error("[project] Failed to create debate MCP server:", e.message);
|
|
597
582
|
}
|
|
583
|
+
|
|
584
|
+
// Browser MCP server (main project only, not mates)
|
|
585
|
+
if (!isMate) {
|
|
586
|
+
try {
|
|
587
|
+
var browserMcp = require("./browser-mcp-server");
|
|
588
|
+
var mcpConfig = browserMcp.create(sendExtensionCommandAny, function () {
|
|
589
|
+
return Object.values(_browserTabList || {});
|
|
590
|
+
}, {
|
|
591
|
+
watchTab: function (tabId) {
|
|
592
|
+
var key = "tab:" + tabId;
|
|
593
|
+
var active = loadContextSources(slug);
|
|
594
|
+
if (active.indexOf(key) === -1) {
|
|
595
|
+
active.push(key);
|
|
596
|
+
saveContextSources(slug, active);
|
|
597
|
+
var _msg = JSON.stringify({ type: "context_sources_state", active: active });
|
|
598
|
+
for (var c of clients) { if (c.readyState === 1) c.send(_msg); }
|
|
599
|
+
}
|
|
600
|
+
return active;
|
|
601
|
+
},
|
|
602
|
+
unwatchTab: function (tabId) {
|
|
603
|
+
var key = "tab:" + tabId;
|
|
604
|
+
var active = loadContextSources(slug);
|
|
605
|
+
var idx = active.indexOf(key);
|
|
606
|
+
if (idx !== -1) {
|
|
607
|
+
active.splice(idx, 1);
|
|
608
|
+
saveContextSources(slug, active);
|
|
609
|
+
var _msg = JSON.stringify({ type: "context_sources_state", active: active });
|
|
610
|
+
for (var c of clients) { if (c.readyState === 1) c.send(_msg); }
|
|
611
|
+
}
|
|
612
|
+
return active;
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
if (mcpConfig) servers[mcpConfig.name || "clay-browser"] = mcpConfig;
|
|
616
|
+
} catch (e) {
|
|
617
|
+
console.error("[project] Failed to create browser MCP server:", e.message);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return Object.keys(servers).length > 0 ? servers : undefined;
|
|
598
622
|
})(),
|
|
599
623
|
onProcessingChanged: onProcessingChanged,
|
|
600
|
-
onTurnDone: isMate ? function (session, preview) {
|
|
624
|
+
onTurnDone: isMate ? function (session, preview) {
|
|
625
|
+
digestDmTurn(session, preview);
|
|
626
|
+
} : null,
|
|
601
627
|
scheduleMessage: function (session, text, resetsAt) {
|
|
602
628
|
scheduleMessage(session, text, resetsAt);
|
|
603
629
|
},
|
|
@@ -1689,6 +1715,28 @@ function createProjectContext(opts) {
|
|
|
1689
1715
|
handleDebateConfirmBrief(ws);
|
|
1690
1716
|
return;
|
|
1691
1717
|
}
|
|
1718
|
+
if (msg.type === "debate_proposal_response") {
|
|
1719
|
+
// Match the most recent pending proposal (proposalId may not be
|
|
1720
|
+
// available on the client since it's not part of the tool input)
|
|
1721
|
+
var _dpKeys = Object.keys(_pendingDebateProposals);
|
|
1722
|
+
if (_dpKeys.length === 0) return;
|
|
1723
|
+
var _dpKey = msg.proposalId || _dpKeys[_dpKeys.length - 1];
|
|
1724
|
+
var pending = _pendingDebateProposals[_dpKey];
|
|
1725
|
+
if (!pending) return;
|
|
1726
|
+
delete _pendingDebateProposals[_dpKey];
|
|
1727
|
+
if (msg.action === "start") {
|
|
1728
|
+
// Set up debate state on the session, then transition to live
|
|
1729
|
+
var _dpSession = getSessionForWs(ws);
|
|
1730
|
+
if (_dpSession) {
|
|
1731
|
+
var _dpMateId = isMate ? path.basename(cwd) : null;
|
|
1732
|
+
handleMcpDebateApproval(_dpSession, pending.briefData, _dpMateId, ws);
|
|
1733
|
+
}
|
|
1734
|
+
pending.resolve({ action: "start" });
|
|
1735
|
+
} else {
|
|
1736
|
+
pending.resolve({ action: "cancel" });
|
|
1737
|
+
}
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1692
1740
|
if (msg.type === "debate_user_floor_response") {
|
|
1693
1741
|
handleDebateUserFloorResponse(ws, msg);
|
|
1694
1742
|
return;
|
|
@@ -4188,7 +4236,8 @@ function createProjectContext(opts) {
|
|
|
4188
4236
|
data: screenshotData,
|
|
4189
4237
|
file: screenshotName,
|
|
4190
4238
|
tabTitle: tabLabel,
|
|
4191
|
-
tabUrl: tabInfo ? tabInfo.url : ""
|
|
4239
|
+
tabUrl: tabInfo ? tabInfo.url : "",
|
|
4240
|
+
tabFavIconUrl: tabInfo ? tabInfo.favIconUrl : ""
|
|
4192
4241
|
});
|
|
4193
4242
|
parts.push("[Screenshot saved: " + screenshotPath + "]");
|
|
4194
4243
|
}
|
|
@@ -4210,7 +4259,8 @@ function createProjectContext(opts) {
|
|
|
4210
4259
|
}
|
|
4211
4260
|
|
|
4212
4261
|
if (tabContextParts.length > 0) {
|
|
4213
|
-
fullText =
|
|
4262
|
+
fullText = "[The following browser tab data is automatically attached as context sources. Do NOT call browser_read_page, browser_console, browser_network, or browser_screenshot for these tabs — the data is already here.]\n\n" +
|
|
4263
|
+
tabContextParts.join("\n\n---\n\n") + "\n\n" + fullText;
|
|
4214
4264
|
}
|
|
4215
4265
|
|
|
4216
4266
|
// If screenshots were captured, send context preview cards and add to SDK images
|
|
@@ -4224,6 +4274,7 @@ function createProjectContext(opts) {
|
|
|
4224
4274
|
tab: {
|
|
4225
4275
|
title: ss.tabTitle || "",
|
|
4226
4276
|
url: ss.tabUrl || "",
|
|
4277
|
+
favIconUrl: ss.tabFavIconUrl || "",
|
|
4227
4278
|
screenshotFile: ss.file
|
|
4228
4279
|
}
|
|
4229
4280
|
};
|
|
@@ -4234,6 +4285,7 @@ function createProjectContext(opts) {
|
|
|
4234
4285
|
tab: {
|
|
4235
4286
|
title: ss.tabTitle || "",
|
|
4236
4287
|
url: ss.tabUrl || "",
|
|
4288
|
+
favIconUrl: ss.tabFavIconUrl || "",
|
|
4237
4289
|
screenshotUrl: "/p/" + slug + "/images/" + ss.file
|
|
4238
4290
|
}
|
|
4239
4291
|
});
|
|
@@ -4307,6 +4359,8 @@ function createProjectContext(opts) {
|
|
|
4307
4359
|
var _debate = attachDebate({
|
|
4308
4360
|
cwd: cwd,
|
|
4309
4361
|
slug: slug,
|
|
4362
|
+
isMate: isMate,
|
|
4363
|
+
projectOwnerId: projectOwnerId,
|
|
4310
4364
|
send: send,
|
|
4311
4365
|
sendTo: sendTo,
|
|
4312
4366
|
sendToSession: sendToSession,
|
|
@@ -4331,6 +4385,7 @@ function createProjectContext(opts) {
|
|
|
4331
4385
|
var handleDebateUserFloorResponse = _debate.handleDebateUserFloorResponse;
|
|
4332
4386
|
var restoreDebateState = _debate.restoreDebateState;
|
|
4333
4387
|
var checkForDmDebateBrief = _debate.checkForDmDebateBrief;
|
|
4388
|
+
var handleMcpDebateApproval = _debate.handleMcpDebateApproval;
|
|
4334
4389
|
|
|
4335
4390
|
// --- Session presence (who is viewing which session) ---
|
|
4336
4391
|
function broadcastPresence() {
|
package/lib/public/app.js
CHANGED
|
@@ -32,7 +32,7 @@ import { initMateWizard, openMateWizard, closeMateWizard, handleMateCreated } fr
|
|
|
32
32
|
import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } from './modules/command-palette.js';
|
|
33
33
|
import { initLongPress } from './modules/longpress.js';
|
|
34
34
|
import { initMention, handleMentionStart, handleMentionStream, handleMentionDone, handleMentionError, handleMentionActivity, renderMentionUser, renderMentionResponse } from './modules/mention.js';
|
|
35
|
-
import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateResumed, handleDebateTurn, handleDebateActivity, handleDebateStream, handleDebateTurnDone, handleDebateCommentQueued, handleDebateCommentInjected, handleDebateEnded, handleDebateError, renderDebateStarted, renderDebateTurnDone, renderDebateEnded, renderDebateCommentInjected, renderDebateUserResume, openDebateModal, closeDebateModal, handleDebateBriefReady, renderDebateBriefReady, isDebateActive, resetDebateState, exportDebateAsPdf } from './modules/debate.js';
|
|
35
|
+
import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateResumed, handleDebateTurn, handleDebateActivity, handleDebateStream, handleDebateTurnDone, handleDebateCommentQueued, handleDebateCommentInjected, handleDebateEnded, handleDebateError, renderDebateStarted, renderDebateTurnDone, renderDebateEnded, renderDebateCommentInjected, renderDebateUserResume, openDebateModal, closeDebateModal, handleDebateBriefReady, renderDebateBriefReady, isDebateActive, resetDebateState, exportDebateAsPdf, renderMcpDebateProposal } from './modules/debate.js';
|
|
36
36
|
|
|
37
37
|
// --- Base path for multi-project routing ---
|
|
38
38
|
var slugMatch = location.pathname.match(/^\/p\/([a-z0-9_-]+)/);
|
|
@@ -4388,6 +4388,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4388
4388
|
if (dhBar) dhBar.remove();
|
|
4389
4389
|
var dbBadges = document.querySelectorAll(".debate-header-badge");
|
|
4390
4390
|
for (var dbi = 0; dbi < dbBadges.length; dbi++) dbBadges[dbi].remove();
|
|
4391
|
+
// Clean up ended mode banner if debate is not active on this session
|
|
4392
|
+
if (debateEndedMode) exitDebateEndedMode();
|
|
4391
4393
|
}
|
|
4392
4394
|
scrollToBottom();
|
|
4393
4395
|
// Scroll to tool element if navigating from file edit history
|
|
@@ -4741,7 +4743,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4741
4743
|
header.className = "context-card-header";
|
|
4742
4744
|
var icon = document.createElement("span");
|
|
4743
4745
|
icon.className = "context-card-icon";
|
|
4744
|
-
icon.
|
|
4746
|
+
icon.innerHTML = iconHtml("globe");
|
|
4745
4747
|
header.appendChild(icon);
|
|
4746
4748
|
var label = document.createElement("span");
|
|
4747
4749
|
label.textContent = "Viewing tab";
|
|
@@ -4765,6 +4767,15 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4765
4767
|
if (tabTitle || tabDomain) {
|
|
4766
4768
|
var meta = document.createElement("div");
|
|
4767
4769
|
meta.className = "context-card-meta";
|
|
4770
|
+
if (msg.tab.favIconUrl) {
|
|
4771
|
+
var fav = document.createElement("img");
|
|
4772
|
+
fav.className = "context-card-favicon";
|
|
4773
|
+
fav.src = msg.tab.favIconUrl;
|
|
4774
|
+
fav.width = 14;
|
|
4775
|
+
fav.height = 14;
|
|
4776
|
+
fav.onerror = function () { this.style.display = "none"; };
|
|
4777
|
+
meta.appendChild(fav);
|
|
4778
|
+
}
|
|
4768
4779
|
var titleEl = document.createElement("span");
|
|
4769
4780
|
titleEl.className = "context-card-title";
|
|
4770
4781
|
titleEl.textContent = tabTitle;
|
|
@@ -4838,6 +4849,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4838
4849
|
}
|
|
4839
4850
|
renderPlanBanner("exit");
|
|
4840
4851
|
getTools()[msg.id] = { el: null, name: msg.name, input: null, done: true, hidden: true };
|
|
4852
|
+
} else if (msg.name === "propose_debate" || (msg.name && msg.name.indexOf("propose_debate") !== -1)) {
|
|
4853
|
+
getTools()[msg.id] = { el: null, name: msg.name, input: null, done: true, hidden: true };
|
|
4841
4854
|
} else if (getTodoTools()[msg.name]) {
|
|
4842
4855
|
getTools()[msg.id] = { el: null, name: msg.name, input: null, done: true, hidden: true };
|
|
4843
4856
|
} else {
|
|
@@ -4846,7 +4859,18 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
4846
4859
|
break;
|
|
4847
4860
|
|
|
4848
4861
|
case "tool_executing":
|
|
4849
|
-
if (msg.name === "
|
|
4862
|
+
if ((msg.name === "propose_debate" || (msg.name && msg.name.indexOf("propose_debate") !== -1)) && msg.input) {
|
|
4863
|
+
var _dpTool = getTools()[msg.id];
|
|
4864
|
+
if (_dpTool) {
|
|
4865
|
+
if (_dpTool.el) _dpTool.el.style.display = "none";
|
|
4866
|
+
_dpTool.done = true;
|
|
4867
|
+
_dpTool.hidden = true;
|
|
4868
|
+
removeToolFromGroup(msg.id);
|
|
4869
|
+
}
|
|
4870
|
+
finalizeAssistantBlock();
|
|
4871
|
+
renderMcpDebateProposal(msg.id, msg.input);
|
|
4872
|
+
startUrgentBlink();
|
|
4873
|
+
} else if (msg.name === "AskUserQuestion" && msg.input && msg.input.questions) {
|
|
4850
4874
|
var askTool = getTools()[msg.id];
|
|
4851
4875
|
if (askTool) {
|
|
4852
4876
|
if (askTool.el) askTool.el.style.display = "none";
|
|
@@ -5902,7 +5926,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
5902
5926
|
// --- Debate module ---
|
|
5903
5927
|
initDebate({
|
|
5904
5928
|
get ws() { return ws; },
|
|
5929
|
+
sendWs: function (obj) { if (ws && ws.readyState === 1) ws.send(JSON.stringify(obj)); },
|
|
5905
5930
|
messagesEl: messagesEl,
|
|
5931
|
+
addToMessages: function (el) { addToMessages(el); },
|
|
5906
5932
|
scrollToBottom: scrollToBottom,
|
|
5907
5933
|
addCopyHandler: addCopyHandler,
|
|
5908
5934
|
matesList: function () { return cachedMatesList || []; },
|
|
@@ -873,7 +873,8 @@
|
|
|
873
873
|
.terminal-tab.active { color: var(--text); border-color: var(--accent); }
|
|
874
874
|
.terminal-tab.exited { opacity: 0.5; }
|
|
875
875
|
|
|
876
|
-
|
|
876
|
+
|
|
877
|
+
.terminal-tab-more {
|
|
877
878
|
display: none;
|
|
878
879
|
align-items: center;
|
|
879
880
|
justify-content: center;
|
|
@@ -888,9 +889,44 @@
|
|
|
888
889
|
line-height: 1;
|
|
889
890
|
}
|
|
890
891
|
|
|
891
|
-
.terminal-tab:hover .terminal-tab-
|
|
892
|
-
.terminal-tab.active .terminal-tab-
|
|
893
|
-
.terminal-tab-
|
|
892
|
+
.terminal-tab:hover .terminal-tab-more,
|
|
893
|
+
.terminal-tab.active .terminal-tab-more { display: flex; }
|
|
894
|
+
.terminal-tab-more:hover { background: rgba(var(--overlay-rgb),0.1); color: var(--text); }
|
|
895
|
+
|
|
896
|
+
.terminal-tab-ctx {
|
|
897
|
+
position: fixed;
|
|
898
|
+
z-index: 9999;
|
|
899
|
+
min-width: 120px;
|
|
900
|
+
background: var(--bg-elevated, var(--bg));
|
|
901
|
+
border: 1px solid var(--border);
|
|
902
|
+
border-radius: 6px;
|
|
903
|
+
padding: 4px;
|
|
904
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
.terminal-tab-ctx-item {
|
|
908
|
+
display: flex;
|
|
909
|
+
align-items: center;
|
|
910
|
+
gap: 8px;
|
|
911
|
+
width: 100%;
|
|
912
|
+
padding: 6px 10px;
|
|
913
|
+
border: none;
|
|
914
|
+
border-radius: 4px;
|
|
915
|
+
background: transparent;
|
|
916
|
+
color: var(--text-secondary);
|
|
917
|
+
font-size: 12px;
|
|
918
|
+
cursor: pointer;
|
|
919
|
+
white-space: nowrap;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
.terminal-tab-ctx-item:hover {
|
|
923
|
+
background: rgba(var(--overlay-rgb),0.08);
|
|
924
|
+
color: var(--text);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.terminal-tab-ctx-danger:hover {
|
|
928
|
+
color: #e74c3c;
|
|
929
|
+
}
|
|
894
930
|
|
|
895
931
|
.terminal-tab-label { cursor: default; }
|
|
896
932
|
|
|
@@ -971,6 +1007,7 @@
|
|
|
971
1007
|
|
|
972
1008
|
.terminal-tab-body .xterm {
|
|
973
1009
|
height: 100%;
|
|
1010
|
+
overflow: hidden;
|
|
974
1011
|
}
|
|
975
1012
|
|
|
976
1013
|
/* --- Terminal key toolbar (mobile) --- */
|
|
@@ -197,8 +197,13 @@
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
.context-card-header .context-card-icon {
|
|
200
|
-
font-size: 13px;
|
|
201
200
|
opacity: 0.7;
|
|
201
|
+
display: inline-flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
}
|
|
204
|
+
.context-card-header .context-card-icon .lucide {
|
|
205
|
+
width: 14px;
|
|
206
|
+
height: 14px;
|
|
202
207
|
}
|
|
203
208
|
|
|
204
209
|
.context-card-screenshot {
|
|
@@ -226,10 +231,16 @@
|
|
|
226
231
|
|
|
227
232
|
.context-card-meta {
|
|
228
233
|
display: flex;
|
|
229
|
-
|
|
230
|
-
align-items: baseline;
|
|
234
|
+
align-items: center;
|
|
231
235
|
margin-top: 8px;
|
|
232
|
-
gap:
|
|
236
|
+
gap: 6px;
|
|
237
|
+
}
|
|
238
|
+
.context-card-favicon {
|
|
239
|
+
width: 14px;
|
|
240
|
+
height: 14px;
|
|
241
|
+
border-radius: 2px;
|
|
242
|
+
flex-shrink: 0;
|
|
243
|
+
object-fit: contain;
|
|
233
244
|
}
|
|
234
245
|
|
|
235
246
|
.context-card-title {
|
|
@@ -240,6 +251,8 @@
|
|
|
240
251
|
text-overflow: ellipsis;
|
|
241
252
|
flex: 1;
|
|
242
253
|
min-width: 0;
|
|
254
|
+
flex: 1;
|
|
255
|
+
min-width: 0;
|
|
243
256
|
}
|
|
244
257
|
|
|
245
258
|
.context-card-domain {
|
package/lib/public/index.html
CHANGED
|
@@ -2040,6 +2040,7 @@
|
|
|
2040
2040
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5/lib/xterm.min.js"></script>
|
|
2041
2041
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0/lib/addon-fit.min.js"></script>
|
|
2042
2042
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0/lib/addon-web-links.min.js"></script>
|
|
2043
|
+
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@0/lib/addon-webgl.min.js"></script>
|
|
2043
2044
|
<script type="module" src="app.js"></script>
|
|
2044
2045
|
<div id="pwa-install-modal" class="pwa-modal hidden">
|
|
2045
2046
|
<div class="pwa-modal-backdrop"></div>
|
|
@@ -690,8 +690,8 @@ export function renderDebateEnded(entry) {
|
|
|
690
690
|
ctx.messagesEl.appendChild(statusLine);
|
|
691
691
|
refreshIcons();
|
|
692
692
|
|
|
693
|
-
//
|
|
694
|
-
|
|
693
|
+
// During history replay, don't activate ended mode banner.
|
|
694
|
+
// The debate is already over; status line in messages is sufficient.
|
|
695
695
|
}
|
|
696
696
|
|
|
697
697
|
export function renderDebateCommentInjected(entry) {
|
|
@@ -1301,3 +1301,117 @@ function renderDebateBriefCard(msg, resolved) {
|
|
|
1301
1301
|
refreshIcons();
|
|
1302
1302
|
ctx.scrollToBottom();
|
|
1303
1303
|
}
|
|
1304
|
+
|
|
1305
|
+
// --- MCP-based debate proposal card ---
|
|
1306
|
+
// Renders an inline card from the propose_debate MCP tool input.
|
|
1307
|
+
// The card reuses the same visual structure as renderDebateBriefCard
|
|
1308
|
+
// but sends debate_proposal_response instead of debate_confirm_brief.
|
|
1309
|
+
|
|
1310
|
+
export function renderMcpDebateProposal(toolId, input) {
|
|
1311
|
+
var panelists = [];
|
|
1312
|
+
try {
|
|
1313
|
+
panelists = typeof input.panelists === "string" ? JSON.parse(input.panelists) : (input.panelists || []);
|
|
1314
|
+
} catch (e) {
|
|
1315
|
+
panelists = [];
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
var el = document.createElement("div");
|
|
1319
|
+
el.className = "debate-brief-card";
|
|
1320
|
+
|
|
1321
|
+
// Header
|
|
1322
|
+
var header = document.createElement("div");
|
|
1323
|
+
header.className = "debate-brief-card-header";
|
|
1324
|
+
header.innerHTML =
|
|
1325
|
+
'<span class="debate-brief-card-icon">' + iconHtml("message-circle") + '</span>' +
|
|
1326
|
+
'<span class="debate-brief-card-title">Debate Proposal</span>' +
|
|
1327
|
+
'<span class="debate-brief-card-chevron">' + iconHtml("chevron-down") + '</span>';
|
|
1328
|
+
|
|
1329
|
+
// Body
|
|
1330
|
+
var body = document.createElement("div");
|
|
1331
|
+
body.className = "debate-brief-card-body";
|
|
1332
|
+
|
|
1333
|
+
var topicHtml = '<div class="debate-brief-topic">' + escapeHtml(input.topic || "Untitled") + '</div>';
|
|
1334
|
+
|
|
1335
|
+
if (input.context) {
|
|
1336
|
+
topicHtml += '<div class="debate-brief-context">' + escapeHtml(input.context) + '</div>';
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// Resolve mate names from matesList
|
|
1340
|
+
var mates = ctx.matesList ? ctx.matesList() : [];
|
|
1341
|
+
var mateMap = {};
|
|
1342
|
+
for (var mi = 0; mi < mates.length; mi++) {
|
|
1343
|
+
mateMap[mates[mi].id] = mates[mi];
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
topicHtml += '<div class="debate-brief-panelists-label">' + iconHtml("users") + ' <strong>Panelists:</strong></div>';
|
|
1347
|
+
topicHtml += '<div class="debate-brief-panelists">';
|
|
1348
|
+
for (var i = 0; i < panelists.length; i++) {
|
|
1349
|
+
var p = panelists[i];
|
|
1350
|
+
var mate = mateMap[p.mateId];
|
|
1351
|
+
var mateName = mate ? (mate.displayName || mate.name || p.mateId) : p.mateId;
|
|
1352
|
+
var avatarSrc = mate ? mateAvatarUrl(mate, 24) : "";
|
|
1353
|
+
topicHtml += '<div class="debate-brief-panelist" style="display:flex;align-items:center;gap:8px;">';
|
|
1354
|
+
if (avatarSrc) {
|
|
1355
|
+
topicHtml += '<img src="' + escapeHtml(avatarSrc) + '" width="24" height="24" style="border-radius:50%;flex-shrink:0;">';
|
|
1356
|
+
}
|
|
1357
|
+
topicHtml += '<span class="debate-brief-panelist-name">' + escapeHtml(mateName) + '</span>';
|
|
1358
|
+
if (p.role) {
|
|
1359
|
+
topicHtml += '<span class="debate-brief-panelist-role">' + escapeHtml(p.role) + '</span>';
|
|
1360
|
+
}
|
|
1361
|
+
topicHtml += '</div>';
|
|
1362
|
+
}
|
|
1363
|
+
topicHtml += '</div>';
|
|
1364
|
+
|
|
1365
|
+
if (input.specialRequests) {
|
|
1366
|
+
topicHtml += '<div class="debate-brief-special">' +
|
|
1367
|
+
iconHtml("info") + ' ' + escapeHtml(input.specialRequests) +
|
|
1368
|
+
'</div>';
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
body.innerHTML = topicHtml;
|
|
1372
|
+
|
|
1373
|
+
// Actions
|
|
1374
|
+
var actions = document.createElement("div");
|
|
1375
|
+
actions.className = "debate-brief-actions";
|
|
1376
|
+
|
|
1377
|
+
var startBtn = document.createElement("button");
|
|
1378
|
+
startBtn.className = "debate-brief-start-btn";
|
|
1379
|
+
startBtn.innerHTML = iconHtml("play") + " Start Debate";
|
|
1380
|
+
|
|
1381
|
+
var cancelBtn = document.createElement("button");
|
|
1382
|
+
cancelBtn.className = "debate-brief-cancel-btn";
|
|
1383
|
+
cancelBtn.textContent = "Cancel";
|
|
1384
|
+
|
|
1385
|
+
startBtn.addEventListener("click", function () {
|
|
1386
|
+
if (ctx.sendWs) {
|
|
1387
|
+
ctx.sendWs({ type: "debate_proposal_response", proposalId: input.proposalId, action: "start" });
|
|
1388
|
+
}
|
|
1389
|
+
el.classList.add("resolved");
|
|
1390
|
+
actions.innerHTML = '<span class="debate-brief-resolved-label">' + iconHtml("check") + ' Starting debate...</span>';
|
|
1391
|
+
refreshIcons();
|
|
1392
|
+
});
|
|
1393
|
+
|
|
1394
|
+
cancelBtn.addEventListener("click", function () {
|
|
1395
|
+
if (ctx.sendWs) {
|
|
1396
|
+
ctx.sendWs({ type: "debate_proposal_response", proposalId: input.proposalId, action: "cancel" });
|
|
1397
|
+
}
|
|
1398
|
+
el.classList.add("resolved");
|
|
1399
|
+
actions.innerHTML = '<span class="debate-brief-resolved-label debate-brief-cancelled">' + iconHtml("x") + ' Cancelled</span>';
|
|
1400
|
+
refreshIcons();
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
actions.appendChild(startBtn);
|
|
1404
|
+
actions.appendChild(cancelBtn);
|
|
1405
|
+
|
|
1406
|
+
// Collapse toggle
|
|
1407
|
+
header.addEventListener("click", function () {
|
|
1408
|
+
el.classList.toggle("collapsed");
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
el.appendChild(header);
|
|
1412
|
+
el.appendChild(body);
|
|
1413
|
+
el.appendChild(actions);
|
|
1414
|
+
ctx.addToMessages(el);
|
|
1415
|
+
refreshIcons();
|
|
1416
|
+
ctx.scrollToBottom();
|
|
1417
|
+
}
|
|
@@ -352,6 +352,16 @@ function createXtermForTab(tab) {
|
|
|
352
352
|
|
|
353
353
|
xterm.open(bodyEl);
|
|
354
354
|
|
|
355
|
+
// WebGL addon: pixel-perfect rendering (eliminates gaps in block characters)
|
|
356
|
+
// Must be loaded after xterm.open() so the rendering context is available.
|
|
357
|
+
if (typeof WebglAddon !== "undefined") {
|
|
358
|
+
try {
|
|
359
|
+
xterm.loadAddon(new WebglAddon.WebglAddon());
|
|
360
|
+
} catch (e) {
|
|
361
|
+
// WebGL not available, fall back to DOM renderer
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
355
365
|
// Route input to server
|
|
356
366
|
xterm.onData(function (data) {
|
|
357
367
|
if (ctx.ws && ctx.connected) {
|
|
@@ -481,14 +491,14 @@ function renderTabBar() {
|
|
|
481
491
|
startRenameTab(t, label);
|
|
482
492
|
});
|
|
483
493
|
|
|
484
|
-
var
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
494
|
+
var moreBtn = document.createElement("button");
|
|
495
|
+
moreBtn.className = "terminal-tab-more";
|
|
496
|
+
moreBtn.innerHTML = '<i data-lucide="ellipsis" style="width:12px;height:12px"></i>';
|
|
497
|
+
moreBtn.addEventListener("click", function (e) {
|
|
488
498
|
e.stopPropagation();
|
|
489
|
-
|
|
499
|
+
showTabContextMenu(e, t, label);
|
|
490
500
|
});
|
|
491
|
-
el.appendChild(
|
|
501
|
+
el.appendChild(moreBtn);
|
|
492
502
|
|
|
493
503
|
el.addEventListener("click", function () {
|
|
494
504
|
if (t.id !== activeTabId) {
|
|
@@ -504,6 +514,52 @@ function renderTabBar() {
|
|
|
504
514
|
refreshIcons();
|
|
505
515
|
}
|
|
506
516
|
|
|
517
|
+
// --- Tab context menu (three-dot) ---
|
|
518
|
+
function showTabContextMenu(e, tab, labelEl) {
|
|
519
|
+
var existing = document.querySelector(".terminal-tab-ctx");
|
|
520
|
+
if (existing) existing.remove();
|
|
521
|
+
|
|
522
|
+
var menu = document.createElement("div");
|
|
523
|
+
menu.className = "terminal-tab-ctx";
|
|
524
|
+
|
|
525
|
+
var renameItem = document.createElement("button");
|
|
526
|
+
renameItem.className = "terminal-tab-ctx-item";
|
|
527
|
+
renameItem.innerHTML = '<i data-lucide="pencil" style="width:13px;height:13px"></i> Rename';
|
|
528
|
+
renameItem.addEventListener("click", function () {
|
|
529
|
+
menu.remove();
|
|
530
|
+
startRenameTab(tab, labelEl);
|
|
531
|
+
});
|
|
532
|
+
menu.appendChild(renameItem);
|
|
533
|
+
|
|
534
|
+
var closeItem = document.createElement("button");
|
|
535
|
+
closeItem.className = "terminal-tab-ctx-item terminal-tab-ctx-danger";
|
|
536
|
+
closeItem.innerHTML = '<i data-lucide="trash-2" style="width:13px;height:13px"></i> Close';
|
|
537
|
+
closeItem.addEventListener("click", function () {
|
|
538
|
+
menu.remove();
|
|
539
|
+
closeTab(tab.id);
|
|
540
|
+
});
|
|
541
|
+
menu.appendChild(closeItem);
|
|
542
|
+
|
|
543
|
+
document.body.appendChild(menu);
|
|
544
|
+
refreshIcons();
|
|
545
|
+
|
|
546
|
+
// Position near the button
|
|
547
|
+
var rect = e.currentTarget.getBoundingClientRect();
|
|
548
|
+
menu.style.top = (rect.bottom + 4) + "px";
|
|
549
|
+
menu.style.left = rect.left + "px";
|
|
550
|
+
|
|
551
|
+
// Dismiss on outside click
|
|
552
|
+
function dismiss(ev) {
|
|
553
|
+
if (!menu.contains(ev.target)) {
|
|
554
|
+
menu.remove();
|
|
555
|
+
document.removeEventListener("mousedown", dismiss, true);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
setTimeout(function () {
|
|
559
|
+
document.addEventListener("mousedown", dismiss, true);
|
|
560
|
+
}, 0);
|
|
561
|
+
}
|
|
562
|
+
|
|
507
563
|
// --- Rename tab inline ---
|
|
508
564
|
function startRenameTab(tab, labelEl) {
|
|
509
565
|
var input = document.createElement("input");
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -1567,24 +1567,15 @@ function createSDKBridge(opts) {
|
|
|
1567
1567
|
|
|
1568
1568
|
// --- SDK query lifecycle ---
|
|
1569
1569
|
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
if (toolName === "AskUserQuestion") {
|
|
1575
|
-
return Promise.resolve({ behavior: "deny", message: "Autonomous mode. Make your own decision." });
|
|
1576
|
-
}
|
|
1577
|
-
if (toolName === "EnterPlanMode") {
|
|
1578
|
-
return Promise.resolve({ behavior: "deny", message: "Do not enter plan mode. Execute directly." });
|
|
1579
|
-
}
|
|
1580
|
-
return Promise.resolve({ behavior: "allow", updatedInput: input });
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1570
|
+
// Check if a tool should be auto-approved based on whitelist rules.
|
|
1571
|
+
// Returns { behavior: "allow", updatedInput } if whitelisted, or null if not.
|
|
1572
|
+
// Shared by handleCanUseTool and mate mention canUseTool handlers.
|
|
1573
|
+
function checkToolWhitelist(toolName, input) {
|
|
1583
1574
|
// Auto-approve read-only tools for ALL sessions.
|
|
1584
1575
|
// These tools only inspect files and fetch data — no side effects.
|
|
1585
1576
|
var readOnlyTools = { Read: true, Glob: true, Grep: true, WebFetch: true, WebSearch: true };
|
|
1586
1577
|
if (readOnlyTools[toolName]) {
|
|
1587
|
-
return
|
|
1578
|
+
return { behavior: "allow", updatedInput: input };
|
|
1588
1579
|
}
|
|
1589
1580
|
|
|
1590
1581
|
// Auto-approve safe browser MCP tools.
|
|
@@ -1595,10 +1586,17 @@ function createSDKBridge(opts) {
|
|
|
1595
1586
|
if (toolName.indexOf("mcp__") === 0 && toolName.indexOf("__browser_") !== -1) {
|
|
1596
1587
|
var mcpToolName = toolName.substring(toolName.lastIndexOf("__") + 2);
|
|
1597
1588
|
if (safeBrowserTools[mcpToolName]) {
|
|
1598
|
-
return
|
|
1589
|
+
return { behavior: "allow", updatedInput: input };
|
|
1599
1590
|
}
|
|
1600
1591
|
}
|
|
1601
1592
|
|
|
1593
|
+
// Auto-approve debate MCP tools (propose_debate).
|
|
1594
|
+
// These are user-facing tools that show inline approval cards,
|
|
1595
|
+
// so the permission prompt is redundant.
|
|
1596
|
+
if (toolName.indexOf("mcp__clay-debate__") === 0) {
|
|
1597
|
+
return { behavior: "allow", updatedInput: input };
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1602
1600
|
// Auto-approve safe Bash commands (read-only, non-destructive)
|
|
1603
1601
|
// Applies to ALL sessions (mates and regular projects alike).
|
|
1604
1602
|
// These are purely read-only commands that cannot modify files, install
|
|
@@ -1663,10 +1661,32 @@ function createSDKBridge(opts) {
|
|
|
1663
1661
|
if (!safeBashCommands[firstWord]) { allSafe = false; break; }
|
|
1664
1662
|
}
|
|
1665
1663
|
if (allSafe) {
|
|
1666
|
-
return
|
|
1664
|
+
return { behavior: "allow", updatedInput: input };
|
|
1667
1665
|
}
|
|
1668
1666
|
}
|
|
1669
1667
|
|
|
1668
|
+
return null; // Not whitelisted
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
function handleCanUseTool(session, toolName, input, opts) {
|
|
1672
|
+
// Ralph Loop execution: auto-approve all tools, deny interactive ones.
|
|
1673
|
+
// Crafting sessions are interactive — user and Claude collaborate to build PROMPT.md / JUDGE.md.
|
|
1674
|
+
if (session.loop && session.loop.active && session.loop.role !== "crafting") {
|
|
1675
|
+
if (toolName === "AskUserQuestion") {
|
|
1676
|
+
return Promise.resolve({ behavior: "deny", message: "Autonomous mode. Make your own decision." });
|
|
1677
|
+
}
|
|
1678
|
+
if (toolName === "EnterPlanMode") {
|
|
1679
|
+
return Promise.resolve({ behavior: "deny", message: "Do not enter plan mode. Execute directly." });
|
|
1680
|
+
}
|
|
1681
|
+
return Promise.resolve({ behavior: "allow", updatedInput: input });
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
// Check shared whitelist (read-only tools, safe browser tools, safe bash commands)
|
|
1685
|
+
var whitelisted = checkToolWhitelist(toolName, input);
|
|
1686
|
+
if (whitelisted) {
|
|
1687
|
+
return Promise.resolve(whitelisted);
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1670
1690
|
// AskUserQuestion: wait for user answers via WebSocket
|
|
1671
1691
|
if (toolName === "AskUserQuestion") {
|
|
1672
1692
|
return new Promise(function(resolve) {
|
|
@@ -2529,6 +2549,7 @@ function createSDKBridge(opts) {
|
|
|
2529
2549
|
return {
|
|
2530
2550
|
createMessageQueue: createMessageQueue,
|
|
2531
2551
|
processSDKMessage: processSDKMessage,
|
|
2552
|
+
checkToolWhitelist: checkToolWhitelist,
|
|
2532
2553
|
handleCanUseTool: handleCanUseTool,
|
|
2533
2554
|
handleElicitation: handleElicitation,
|
|
2534
2555
|
processQueryStream: processQueryStream,
|
package/package.json
CHANGED