clay-server 4.7.0-beta.3 → 4.7.0-beta.5
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.
|
@@ -387,6 +387,7 @@ function attachPairResultOutbox(options) {
|
|
|
387
387
|
markRestartUncertain: markRestartUncertain,
|
|
388
388
|
authorizeHumanTurn: authorizeHumanTurn,
|
|
389
389
|
grantManualRetry: grantManualRetry,
|
|
390
|
+
dismissNotice: function (key, revision) { return transition(key, { dismissedNoticeRevision: revision }); },
|
|
390
391
|
status: status,
|
|
391
392
|
list: function () { return Object.keys(records).map(function (key) { return copy(records[key]); }); },
|
|
392
393
|
get: function (key) { return records[key] ? copy(records[key]) : null; },
|
|
@@ -146,6 +146,11 @@ function attachPairResultRecovery(ctx) {
|
|
|
146
146
|
confirmDuplicate: completed && state === "uncertain",
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
|
+
function noticeItem(record) {
|
|
150
|
+
var item = publicItem(record);
|
|
151
|
+
item.revision = opaqueId(JSON.stringify(item));
|
|
152
|
+
return item;
|
|
153
|
+
}
|
|
149
154
|
function canRead(ws, session) {
|
|
150
155
|
if (!ws || !session || ws._clayActiveSession !== session.localId) return false;
|
|
151
156
|
if (ownerId(session)) return !!(ws._clayUser && String(ws._clayUser.id) === String(ownerId(session)));
|
|
@@ -153,7 +158,9 @@ function attachPairResultRecovery(ctx) {
|
|
|
153
158
|
}
|
|
154
159
|
function sendState(session, ws, sendFn) {
|
|
155
160
|
if (!canRead(ws, session)) return false;
|
|
156
|
-
var items = recordsFor(session).
|
|
161
|
+
var items = recordsFor(session).filter(function (record) {
|
|
162
|
+
return record.dismissedNoticeRevision !== noticeItem(record).revision;
|
|
163
|
+
}).map(noticeItem);
|
|
157
164
|
(sendFn || ctx.sendTo.bind(null, ws))({ type: "pair_result_status", sessionId: session.localId, items: items });
|
|
158
165
|
return true;
|
|
159
166
|
}
|
|
@@ -204,7 +211,7 @@ function attachPairResultRecovery(ctx) {
|
|
|
204
211
|
changed(session);
|
|
205
212
|
}
|
|
206
213
|
function handleMessage(ws, msg) {
|
|
207
|
-
if (!msg || (msg.type !== "pair_result_state_request" && msg.type !== "pair_result_retry")) return false;
|
|
214
|
+
if (!msg || (msg.type !== "pair_result_state_request" && msg.type !== "pair_result_retry" && msg.type !== "pair_result_dismiss")) return false;
|
|
208
215
|
var session = ctx.sm.sessions.get(ws._clayActiveSession);
|
|
209
216
|
if (!canRead(ws, session)) return true;
|
|
210
217
|
if (msg.sessionId != null && msg.sessionId !== session.localId) return true;
|
|
@@ -213,6 +220,12 @@ function attachPairResultRecovery(ctx) {
|
|
|
213
220
|
var records = recordsFor(session), record = null;
|
|
214
221
|
for (var i = 0; i < records.length; i++) if (opaqueId(records[i].key) === msg.id) record = records[i];
|
|
215
222
|
if (!record) { sendState(session, ws); return true; }
|
|
223
|
+
if (msg.type === "pair_result_dismiss") {
|
|
224
|
+
if (msg.revision === noticeItem(record).revision) ctx.outbox.dismissNotice(record.key, msg.revision);
|
|
225
|
+
changed(session);
|
|
226
|
+
sendState(session, ws);
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
216
229
|
if (record.deliveryState === "uncertain" && msg.confirmDuplicate !== true) { sendState(session, ws); return true; }
|
|
217
230
|
var granted = ctx.outbox.grantManualRetry(record.key);
|
|
218
231
|
if (granted.ok) schedule(record.key);
|
|
@@ -1387,17 +1387,24 @@ pre.mermaid-error {
|
|
|
1387
1387
|
.plan-banner.plan-exit { color: var(--success); }
|
|
1388
1388
|
.plan-banner.plan-exit .plan-banner-icon { color: var(--success); }
|
|
1389
1389
|
|
|
1390
|
-
|
|
1391
|
-
.pair-result-status
|
|
1390
|
+
/* Recovery notices use the same quiet surface as session work references. */
|
|
1391
|
+
.pair-result-status { flex: 0 0 auto; margin: 8px 16px 0; padding: 8px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg-alt); color: var(--text-muted); font-size: 12px; line-height: 1.5; max-height: 35vh; overflow-y: auto; }
|
|
1392
|
+
.pair-result-status-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; color: var(--text-secondary); }
|
|
1393
|
+
.pair-result-status button { font: inherit; color: var(--text-secondary); background: transparent; border: 1px solid var(--border); border-radius: 5px; cursor: pointer; }
|
|
1394
|
+
.pair-result-status .pair-result-status-dismiss { display: grid; place-items: center; flex: 0 0 28px; width: 28px; height: 28px; padding: 0; border: 0; font-size: 20px; line-height: 1; }
|
|
1395
|
+
.pair-result-status button:hover { background: var(--input-bg); color: var(--text); }
|
|
1396
|
+
.pair-result-status button:focus-visible, .pair-result-status summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
1397
|
+
.pair-result-status-details > summary { cursor: pointer; width: fit-content; }
|
|
1398
|
+
.pair-result-status-card { padding: 10px 0; border-top: 1px solid var(--border); margin-top: 8px; }
|
|
1392
1399
|
.pair-result-status-card > strong, .pair-result-status-card > span { display: block; }
|
|
1393
|
-
.pair-result-status-card > strong { color: var(--text); margin-bottom: 3px; }
|
|
1400
|
+
.pair-result-status-card > strong { color: var(--text-secondary); font-weight: 500; margin-bottom: 3px; }
|
|
1394
1401
|
.pair-result-status-card details { margin-top: 7px; }
|
|
1395
|
-
.pair-result-status-card summary { cursor: pointer;
|
|
1396
|
-
.pair-result-status-card pre { max-height: 180px; overflow: auto; white-space: pre-wrap; font: inherit; }
|
|
1397
|
-
.pair-result-status-
|
|
1398
|
-
.pair-result-status-retry:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
1402
|
+
.pair-result-status-card summary { cursor: pointer; }
|
|
1403
|
+
.pair-result-status-card pre { max-height: 180px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; font: inherit; }
|
|
1404
|
+
.pair-result-status-retry { margin-top: 8px; padding: 4px 8px; }
|
|
1399
1405
|
.pair-result-status-retry:disabled { cursor: not-allowed; opacity: 0.55; }
|
|
1400
|
-
.pair-result-status-
|
|
1406
|
+
@media (max-width: 600px) { .pair-result-status { margin: 6px 8px 0; } .pair-result-status .pair-result-status-dismiss { min-width: 36px; height: 36px; } }
|
|
1407
|
+
|
|
1401
1408
|
|
|
1402
1409
|
/* --- Plan card --- */
|
|
1403
1410
|
.plan-card {
|
|
@@ -33,6 +33,16 @@ function retry(item) {
|
|
|
33
33
|
sendRetry(source, false);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function dismiss(items) {
|
|
37
|
+
var ws = getWs();
|
|
38
|
+
if (!ws || ws.readyState !== 1) return;
|
|
39
|
+
items.forEach(function (item) {
|
|
40
|
+
ws.send(JSON.stringify({ type: "pair_result_dismiss", id: item.id, revision: item.revision,
|
|
41
|
+
sessionId: store.get("activeSessionId"),
|
|
42
|
+
projectSlug: store.get("currentSlug") || store.get("activeProjectSlug") || null }));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
function renderPairResultStatus() {
|
|
37
47
|
var existing = document.getElementById("pair-result-status");
|
|
38
48
|
if (existing) existing.remove();
|
|
@@ -44,6 +54,21 @@ function renderPairResultStatus() {
|
|
|
44
54
|
section.id = "pair-result-status";
|
|
45
55
|
section.className = "pair-result-status";
|
|
46
56
|
section.setAttribute("role", "status");
|
|
57
|
+
var header = document.createElement("div");
|
|
58
|
+
header.className = "pair-result-status-header";
|
|
59
|
+
var heading = document.createElement("span");
|
|
60
|
+
heading.textContent = items.length === 1 ? "Worker update needs attention" : items.length + " Worker updates need attention";
|
|
61
|
+
var close = document.createElement("button");
|
|
62
|
+
close.type = "button"; close.className = "pair-result-status-dismiss";
|
|
63
|
+
close.textContent = "×";
|
|
64
|
+
close.setAttribute("aria-label", "Dismiss Worker notices");
|
|
65
|
+
close.title = "Dismiss notices. Retained tasks and results are kept.";
|
|
66
|
+
close.addEventListener("click", dismiss.bind(null, items));
|
|
67
|
+
header.appendChild(heading); header.appendChild(close); section.appendChild(header);
|
|
68
|
+
var content = document.createElement("details"), toggle = document.createElement("summary");
|
|
69
|
+
content.className = "pair-result-status-details";
|
|
70
|
+
toggle.textContent = "Review updates";
|
|
71
|
+
content.appendChild(toggle); section.appendChild(content);
|
|
47
72
|
for (var i = 0; i < items.length; i++) {
|
|
48
73
|
var item = items[i], card = document.createElement("div");
|
|
49
74
|
card.className = "pair-result-status-card pair-result-status-" + item.state;
|
|
@@ -61,11 +86,11 @@ function renderPairResultStatus() {
|
|
|
61
86
|
}
|
|
62
87
|
if (item.canRetry) {
|
|
63
88
|
var button = document.createElement("button");
|
|
64
|
-
button.type = "button"; button.className = "
|
|
89
|
+
button.type = "button"; button.className = "pair-result-status-retry"; button.textContent = "Retry delivery";
|
|
65
90
|
button.addEventListener("click", retry.bind(null, item));
|
|
66
91
|
card.appendChild(button);
|
|
67
92
|
}
|
|
68
|
-
|
|
93
|
+
content.appendChild(card);
|
|
69
94
|
}
|
|
70
95
|
messages.parentElement.insertBefore(section, messages);
|
|
71
96
|
}
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -18,37 +18,7 @@ var sessionTitlePolicy = require("./session-title-policy");
|
|
|
18
18
|
var sessionTitleGenerator = require("./session-title-generator");
|
|
19
19
|
var loopInterview = require("./project-loop-interview");
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
// Used for IPC to worker processes (McpSdkServerConfigWithInstance is not serializable).
|
|
23
|
-
function extractMcpDescriptors(mcpServers) {
|
|
24
|
-
if (!mcpServers) return null;
|
|
25
|
-
var toJSONSchema;
|
|
26
|
-
try { toJSONSchema = require("zod").toJSONSchema; } catch (e) { return null; }
|
|
27
|
-
var descriptors = [];
|
|
28
|
-
var names = Object.keys(mcpServers);
|
|
29
|
-
for (var i = 0; i < names.length; i++) {
|
|
30
|
-
var serverName = names[i];
|
|
31
|
-
var server = mcpServers[serverName];
|
|
32
|
-
if (!server || !server.instance || !server.instance._registeredTools) continue;
|
|
33
|
-
var tools = [];
|
|
34
|
-
var toolNames = Object.keys(server.instance._registeredTools);
|
|
35
|
-
for (var j = 0; j < toolNames.length; j++) {
|
|
36
|
-
var toolName = toolNames[j];
|
|
37
|
-
var toolDef = server.instance._registeredTools[toolName];
|
|
38
|
-
var inputSchema = { type: "object", properties: {} };
|
|
39
|
-
try {
|
|
40
|
-
if (toolDef.inputSchema) inputSchema = toJSONSchema(toolDef.inputSchema);
|
|
41
|
-
} catch (e) { /* fallback to empty schema */ }
|
|
42
|
-
tools.push({
|
|
43
|
-
name: toolName,
|
|
44
|
-
description: toolDef.description || toolName,
|
|
45
|
-
inputSchema: inputSchema,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
if (tools.length > 0) descriptors.push({ serverName: serverName, tools: tools });
|
|
49
|
-
}
|
|
50
|
-
return descriptors.length > 0 ? descriptors : null;
|
|
51
|
-
}
|
|
21
|
+
var extractMcpDescriptors = require("./sdk-mcp-descriptors").extractMcpDescriptors;
|
|
52
22
|
|
|
53
23
|
// Call an MCP tool handler by server name and tool name.
|
|
54
24
|
// Returns a promise that resolves with the tool result.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Extract serializable tool descriptors from MCP server instances.
|
|
2
|
+
// Used for IPC to worker processes (McpSdkServerConfigWithInstance is not serializable).
|
|
3
|
+
function extractMcpDescriptors(mcpServers) {
|
|
4
|
+
if (!mcpServers) return null;
|
|
5
|
+
// Claude's adapter uses Zod v3 for JSON Schema tools; other servers use v4.
|
|
6
|
+
// Use the MCP SDK's version-aware converter for both kinds of registered tool.
|
|
7
|
+
var toJSONSchema = require("@modelcontextprotocol/sdk/server/zod-json-schema-compat.js").toJsonSchemaCompat;
|
|
8
|
+
var descriptors = [];
|
|
9
|
+
var names = Object.keys(mcpServers);
|
|
10
|
+
for (var i = 0; i < names.length; i++) {
|
|
11
|
+
var serverName = names[i];
|
|
12
|
+
var server = mcpServers[serverName];
|
|
13
|
+
if (!server || !server.instance || !server.instance._registeredTools) continue;
|
|
14
|
+
var tools = [];
|
|
15
|
+
var toolNames = Object.keys(server.instance._registeredTools);
|
|
16
|
+
for (var j = 0; j < toolNames.length; j++) {
|
|
17
|
+
var toolName = toolNames[j];
|
|
18
|
+
var toolDef = server.instance._registeredTools[toolName];
|
|
19
|
+
var inputSchema = { type: "object", properties: {} };
|
|
20
|
+
try {
|
|
21
|
+
if (toolDef.inputSchema) inputSchema = toJSONSchema(toolDef.inputSchema);
|
|
22
|
+
} catch (e) {
|
|
23
|
+
throw new Error("Cannot serialize MCP tool schema " + serverName + "/" + toolName + ": " + e.message);
|
|
24
|
+
}
|
|
25
|
+
tools.push({
|
|
26
|
+
name: toolName,
|
|
27
|
+
description: toolDef.description || toolName,
|
|
28
|
+
inputSchema: inputSchema,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (tools.length > 0) descriptors.push({ serverName: serverName, tools: tools });
|
|
32
|
+
}
|
|
33
|
+
return descriptors.length > 0 ? descriptors : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = { extractMcpDescriptors: extractMcpDescriptors };
|
package/package.json
CHANGED