clay-server 4.7.1-beta.2 → 4.7.1-beta.3
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/project-elicitation-response.js +42 -0
- package/lib/project-sessions.js +2 -14
- package/lib/public/css/elicitation-form.css +33 -0
- package/lib/public/modules/app-messages.js +5 -1
- package/lib/public/modules/elicitation-form.js +260 -0
- package/lib/public/modules/elicitation-schema.js +139 -0
- package/lib/public/modules/tools.js +14 -179
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +7 -4
- package/lib/sdk-worker.js +1 -1
- package/lib/ws-schema.js +1 -0
- package/lib/yoke/acp-agent-profiles.js +1 -1
- package/lib/yoke/acp-event-normalizer.js +17 -3
- package/lib/yoke/acp-query-handle.js +9 -9
- package/lib/yoke/acp-user-input.js +70 -0
- package/lib/yoke/adapters/acp.js +22 -5
- package/lib/yoke/adapters/antigravity.js +55 -6
- package/lib/yoke/adapters/claude-worker.js +1 -1
- package/lib/yoke/adapters/codex.js +5 -2
- package/lib/yoke/adapters/kiro.js +16 -17
- package/lib/yoke/claude-user-input.js +2 -4
- package/lib/yoke/codex-user-input.js +7 -4
- package/lib/yoke/elicitation-schema.js +204 -0
- package/lib/yoke/interface.js +12 -7
- package/lib/yoke/user-input.js +34 -25
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ import { store } from './store.js';
|
|
|
9
9
|
import { applyPermissionHints } from './permission-hints.js';
|
|
10
10
|
import { VENDOR_NAMES } from './app-rendering.js';
|
|
11
11
|
import { startThinkingSegment, appendThinkingText, stopThinkingSegment, resetThinkingTurn, clearThinkingState, saveThinkingState, restoreThinkingState, updateThinkingTokenEstimate } from './thinking-lifecycle.js';
|
|
12
|
+
import { buildElicitationCard, markElicitationCard, showElicitationError } from './elicitation-form.js';
|
|
12
13
|
import { findAskUserCard, getAskUserRequestKey, hasAskUserRequest, rememberAskUserRequest, markAskUserRequestAnswered, saveAskUserState, restoreAskUserState, resetAskUserState } from './ask-user-state.js';
|
|
13
14
|
|
|
14
15
|
var ctx;
|
|
@@ -1026,204 +1027,38 @@ export function markPermissionCancelled(requestId) {
|
|
|
1026
1027
|
}
|
|
1027
1028
|
|
|
1028
1029
|
// --- MCP elicitation rendering ---
|
|
1030
|
+
// Form building, validation and resolved states live in elicitation-form.js.
|
|
1029
1031
|
|
|
1030
1032
|
var pendingElicitations = {};
|
|
1031
1033
|
|
|
1034
|
+
function findElicitationCard(requestId) {
|
|
1035
|
+
return pendingElicitations[requestId]
|
|
1036
|
+
|| ctx.messagesEl.querySelector('.elicitation-container[data-request-id="' + requestId + '"]');
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1032
1039
|
export function renderElicitationRequest(msg) {
|
|
1033
1040
|
if (pendingElicitations[msg.requestId]) return;
|
|
1034
1041
|
ctx.finalizeAssistantBlock();
|
|
1035
1042
|
stopThinking();
|
|
1036
1043
|
closeToolGroup();
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
container.className = "permission-container elicitation-container";
|
|
1040
|
-
container.dataset.requestId = msg.requestId;
|
|
1041
|
-
|
|
1042
|
-
// Header
|
|
1043
|
-
var header = document.createElement("div");
|
|
1044
|
-
header.className = "permission-header";
|
|
1045
|
-
header.innerHTML =
|
|
1046
|
-
'<span class="permission-icon">' + iconHtml("key") + '</span>' +
|
|
1047
|
-
'<span class="permission-title">' + escapeHtml(msg.serverName || "MCP Server") + ' requests input</span>';
|
|
1048
|
-
|
|
1049
|
-
// Body
|
|
1050
|
-
var body = document.createElement("div");
|
|
1051
|
-
body.className = "permission-body";
|
|
1052
|
-
|
|
1053
|
-
if (msg.message) {
|
|
1054
|
-
var messageEl = document.createElement("div");
|
|
1055
|
-
messageEl.className = "permission-reason";
|
|
1056
|
-
messageEl.textContent = msg.message;
|
|
1057
|
-
body.appendChild(messageEl);
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
// Form fields (form mode) or URL button (url mode)
|
|
1061
|
-
var formData = {};
|
|
1062
|
-
|
|
1063
|
-
if (msg.mode === "url" && msg.url) {
|
|
1064
|
-
var urlInfo = document.createElement("div");
|
|
1065
|
-
urlInfo.className = "elicitation-url-info";
|
|
1066
|
-
urlInfo.style.cssText = "margin-top: 8px; font-size: 12px; color: var(--text-muted);";
|
|
1067
|
-
urlInfo.textContent = "Opens: " + msg.url;
|
|
1068
|
-
body.appendChild(urlInfo);
|
|
1069
|
-
} else if (msg.requestedSchema && msg.requestedSchema.properties) {
|
|
1070
|
-
var formEl = document.createElement("div");
|
|
1071
|
-
formEl.className = "elicitation-form";
|
|
1072
|
-
formEl.style.cssText = "margin-top: 8px; display: flex; flex-direction: column; gap: 8px;";
|
|
1073
|
-
|
|
1074
|
-
var props = msg.requestedSchema.properties;
|
|
1075
|
-
var required = msg.requestedSchema.required || [];
|
|
1076
|
-
var propNames = Object.keys(props);
|
|
1077
|
-
for (var i = 0; i < propNames.length; i++) {
|
|
1078
|
-
var propName = propNames[i];
|
|
1079
|
-
var prop = props[propName];
|
|
1080
|
-
var isRequired = required.indexOf(propName) !== -1;
|
|
1081
|
-
|
|
1082
|
-
var fieldWrapper = document.createElement("div");
|
|
1083
|
-
fieldWrapper.style.cssText = "display: flex; flex-direction: column; gap: 2px;";
|
|
1084
|
-
|
|
1085
|
-
var label = document.createElement("label");
|
|
1086
|
-
label.style.cssText = "font-size: 12px; font-weight: 500; color: var(--text-secondary);";
|
|
1087
|
-
label.textContent = propName + (isRequired ? " *" : "");
|
|
1088
|
-
if (prop.description) {
|
|
1089
|
-
label.title = prop.description;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
var input;
|
|
1093
|
-
if (prop.type === "boolean") {
|
|
1094
|
-
input = document.createElement("input");
|
|
1095
|
-
input.type = "checkbox";
|
|
1096
|
-
input.dataset.propName = propName;
|
|
1097
|
-
input.dataset.propType = "boolean";
|
|
1098
|
-
} else if (prop.enum) {
|
|
1099
|
-
input = document.createElement("select");
|
|
1100
|
-
input.dataset.propName = propName;
|
|
1101
|
-
input.dataset.propType = "enum";
|
|
1102
|
-
input.style.cssText = "padding: 4px 8px; border-radius: 4px; border: 1px solid var(--border); background: var(--input-bg); color: var(--text-primary); font-size: 13px;";
|
|
1103
|
-
for (var ei = 0; ei < prop.enum.length; ei++) {
|
|
1104
|
-
var opt = document.createElement("option");
|
|
1105
|
-
opt.value = prop.enum[ei];
|
|
1106
|
-
opt.textContent = prop.enum[ei];
|
|
1107
|
-
input.appendChild(opt);
|
|
1108
|
-
}
|
|
1109
|
-
} else {
|
|
1110
|
-
input = document.createElement("input");
|
|
1111
|
-
input.type = prop.type === "number" || prop.type === "integer" ? "number" : "text";
|
|
1112
|
-
input.dataset.propName = propName;
|
|
1113
|
-
input.dataset.propType = prop.type || "string";
|
|
1114
|
-
input.placeholder = prop.description || propName;
|
|
1115
|
-
input.style.cssText = "padding: 4px 8px; border-radius: 4px; border: 1px solid var(--border); background: var(--input-bg); color: var(--text-primary); font-size: 13px;";
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
fieldWrapper.appendChild(label);
|
|
1119
|
-
fieldWrapper.appendChild(input);
|
|
1120
|
-
formEl.appendChild(fieldWrapper);
|
|
1121
|
-
}
|
|
1122
|
-
body.appendChild(formEl);
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
// Actions
|
|
1126
|
-
var actions = document.createElement("div");
|
|
1127
|
-
actions.className = "permission-actions";
|
|
1128
|
-
|
|
1129
|
-
var acceptBtn = document.createElement("button");
|
|
1130
|
-
acceptBtn.className = "permission-btn permission-allow";
|
|
1131
|
-
|
|
1132
|
-
if (msg.mode === "url" && msg.url) {
|
|
1133
|
-
acceptBtn.textContent = "Open & Approve";
|
|
1134
|
-
acceptBtn.addEventListener("click", function () {
|
|
1135
|
-
window.open(msg.url, "_blank");
|
|
1136
|
-
sendElicitationResponse(container, msg.requestId, "accept", {});
|
|
1137
|
-
});
|
|
1138
|
-
} else {
|
|
1139
|
-
acceptBtn.textContent = "Submit";
|
|
1140
|
-
acceptBtn.addEventListener("click", function () {
|
|
1141
|
-
// Collect form values
|
|
1142
|
-
var content = {};
|
|
1143
|
-
var inputs = container.querySelectorAll("[data-prop-name]");
|
|
1144
|
-
for (var j = 0; j < inputs.length; j++) {
|
|
1145
|
-
var inp = inputs[j];
|
|
1146
|
-
var name = inp.dataset.propName;
|
|
1147
|
-
var pType = inp.dataset.propType;
|
|
1148
|
-
if (pType === "boolean") {
|
|
1149
|
-
content[name] = inp.checked;
|
|
1150
|
-
} else if (pType === "number" || pType === "integer") {
|
|
1151
|
-
content[name] = Number(inp.value);
|
|
1152
|
-
} else {
|
|
1153
|
-
content[name] = inp.value;
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
sendElicitationResponse(container, msg.requestId, "accept", content);
|
|
1157
|
-
});
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
var denyBtn = document.createElement("button");
|
|
1161
|
-
denyBtn.className = "permission-btn permission-deny";
|
|
1162
|
-
denyBtn.textContent = "Deny";
|
|
1163
|
-
denyBtn.addEventListener("click", function () {
|
|
1164
|
-
sendElicitationResponse(container, msg.requestId, "reject", null);
|
|
1044
|
+
var container = buildElicitationCard(msg, function () {
|
|
1045
|
+
if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
|
|
1165
1046
|
});
|
|
1166
|
-
|
|
1167
|
-
actions.appendChild(acceptBtn);
|
|
1168
|
-
actions.appendChild(denyBtn);
|
|
1169
|
-
|
|
1170
|
-
container.appendChild(header);
|
|
1171
|
-
container.appendChild(body);
|
|
1172
|
-
container.appendChild(actions);
|
|
1173
1047
|
ctx.addToMessages(container);
|
|
1174
|
-
|
|
1175
1048
|
pendingElicitations[msg.requestId] = container;
|
|
1176
1049
|
refreshIcons();
|
|
1177
1050
|
ctx.setActivity(null);
|
|
1178
1051
|
maybeScrollToBottom();
|
|
1179
1052
|
}
|
|
1180
1053
|
|
|
1181
|
-
function
|
|
1182
|
-
|
|
1183
|
-
container.classList.add("resolved");
|
|
1184
|
-
if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
|
|
1185
|
-
|
|
1186
|
-
var label = action === "reject" ? "Denied" : "Submitted";
|
|
1187
|
-
var resolvedClass = action === "reject" ? "resolved-denied" : "resolved-allowed";
|
|
1188
|
-
container.classList.add(resolvedClass);
|
|
1189
|
-
|
|
1190
|
-
var actions = container.querySelector(".permission-actions");
|
|
1191
|
-
if (actions) {
|
|
1192
|
-
actions.innerHTML = '<span class="permission-decision-label">' + label + '</span>';
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
if (ctx.ws && ctx.connected) {
|
|
1196
|
-
var msg = {
|
|
1197
|
-
type: "elicitation_response",
|
|
1198
|
-
requestId: requestId,
|
|
1199
|
-
action: action,
|
|
1200
|
-
};
|
|
1201
|
-
if (action === "accept" && content) {
|
|
1202
|
-
msg.content = content;
|
|
1203
|
-
}
|
|
1204
|
-
ctx.ws.send(JSON.stringify(msg));
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1054
|
+
export function markElicitationResolved(requestId, action) {
|
|
1055
|
+
markElicitationCard(findElicitationCard(requestId), action);
|
|
1207
1056
|
delete pendingElicitations[requestId];
|
|
1208
1057
|
}
|
|
1209
1058
|
|
|
1210
|
-
export function
|
|
1211
|
-
|
|
1212
|
-
if (
|
|
1213
|
-
container = ctx.messagesEl.querySelector('.elicitation-container[data-request-id="' + requestId + '"]');
|
|
1214
|
-
}
|
|
1215
|
-
if (!container || container.classList.contains("resolved")) return;
|
|
1216
|
-
|
|
1217
|
-
container.classList.add("resolved");
|
|
1218
|
-
var isDeny = action === "reject";
|
|
1219
|
-
container.classList.add(isDeny ? "resolved-denied" : "resolved-allowed");
|
|
1220
|
-
|
|
1221
|
-
var label = isDeny ? "Denied" : "Submitted";
|
|
1222
|
-
var actionsEl = container.querySelector(".permission-actions");
|
|
1223
|
-
if (actionsEl) {
|
|
1224
|
-
actionsEl.innerHTML = '<span class="permission-decision-label">' + label + '</span>';
|
|
1225
|
-
}
|
|
1226
|
-
delete pendingElicitations[requestId];
|
|
1059
|
+
export function markElicitationError(requestId, error, expired) {
|
|
1060
|
+
showElicitationError(findElicitationCard(requestId), error, expired);
|
|
1061
|
+
if (expired) delete pendingElicitations[requestId];
|
|
1227
1062
|
}
|
|
1228
1063
|
|
|
1229
1064
|
// --- Host user dialog (SDK request_user_dialog) ---
|
package/lib/public/style.css
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
@import url("css/messages.css?v=20260828-delegated-muted1");
|
|
14
14
|
@import url("css/generated-images.css?v=20260829-save-details1");
|
|
15
15
|
@import url("css/rewind.css");
|
|
16
|
+
@import url("css/elicitation-form.css");
|
|
16
17
|
@import url("css/input.css?v=20261002-stop-inline1");
|
|
17
18
|
@import url("css/pending-message-queue.css?v=20260914-queue1");
|
|
18
19
|
@import url("css/filebrowser.css?v=20260829-terminal-canvas1");
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -392,9 +392,9 @@ function createSDKBridge(opts) {
|
|
|
392
392
|
// --- MCP elicitation ---
|
|
393
393
|
|
|
394
394
|
function handleElicitation(session, request, opts) {
|
|
395
|
-
// Ralph Loop:
|
|
395
|
+
// Ralph Loop: autonomous mode explicitly declines elicitation.
|
|
396
396
|
if (session.loop && session.loop.active && session.loop.role !== "crafting") {
|
|
397
|
-
return Promise.resolve({ action: "
|
|
397
|
+
return Promise.resolve({ action: "decline" });
|
|
398
398
|
}
|
|
399
399
|
|
|
400
400
|
return new Promise(function(resolve) {
|
|
@@ -427,8 +427,10 @@ function createSDKBridge(opts) {
|
|
|
427
427
|
|
|
428
428
|
if (opts.signal) {
|
|
429
429
|
opts.signal.addEventListener("abort", function() {
|
|
430
|
+
if (!session.pendingElicitations || !session.pendingElicitations[requestId]) return;
|
|
430
431
|
delete session.pendingElicitations[requestId];
|
|
431
|
-
|
|
432
|
+
sendAndRecord(session, { type: "elicitation_resolved", requestId: requestId, action: "cancel" });
|
|
433
|
+
resolve({ action: "cancel" });
|
|
432
434
|
});
|
|
433
435
|
}
|
|
434
436
|
});
|
|
@@ -1849,7 +1851,8 @@ function createSDKBridge(opts) {
|
|
|
1849
1851
|
}
|
|
1850
1852
|
return handleElicitation(session, request.diagnostics.elicitation, { signal: elicitationController.signal }).then(function(result) {
|
|
1851
1853
|
if (result && result.action === "accept") return respond.submitContent(result.content || {});
|
|
1852
|
-
return respond.
|
|
1854
|
+
if (result && (result.action === "decline" || result.action === "reject")) return respond.decline("Elicitation declined");
|
|
1855
|
+
return respond.cancel("Elicitation cancelled");
|
|
1853
1856
|
});
|
|
1854
1857
|
}
|
|
1855
1858
|
if (typeof opts.onUserInputRequest !== "function") return respond.cancel("Structured user input is unavailable.");
|
package/lib/sdk-worker.js
CHANGED
|
@@ -186,7 +186,7 @@ function onElicitation(request, opts) {
|
|
|
186
186
|
if (opts.signal) {
|
|
187
187
|
opts.signal.addEventListener("abort", function() {
|
|
188
188
|
delete pendingElicitations[requestId];
|
|
189
|
-
resolve({ action: "
|
|
189
|
+
resolve({ action: "cancel" });
|
|
190
190
|
});
|
|
191
191
|
}
|
|
192
192
|
});
|
package/lib/ws-schema.js
CHANGED
|
@@ -194,6 +194,7 @@ var schema = {
|
|
|
194
194
|
"elicitation_request": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Structured form elicitation request from assistant" },
|
|
195
195
|
"elicitation_response": { direction: "c2s", handler: "lib/project-sessions.js", description: "User response to an elicitation form" },
|
|
196
196
|
"elicitation_resolved": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Elicitation request resolved" },
|
|
197
|
+
"elicitation_error": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Submitted elicitation form failed server validation; the request stays pending" },
|
|
197
198
|
"user_dialog_request": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Host dialog request from assistant (SDK request_user_dialog)" },
|
|
198
199
|
"user_dialog_response": { direction: "c2s", handler: "lib/project-sessions.js", description: "User response to a host dialog request" },
|
|
199
200
|
"user_dialog_resolved": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Host dialog request resolved" },
|
|
@@ -185,7 +185,7 @@ var ACP_AGENT_PROFILES = {
|
|
|
185
185
|
displayName: "Grok Build",
|
|
186
186
|
binaryName: "grok",
|
|
187
187
|
overrideName: "GROK_CLI_PATH",
|
|
188
|
-
args: ["--no-auto-update", "--permission-mode", "
|
|
188
|
+
args: ["--no-auto-update", "--permission-mode", "default", "agent", "stdio"],
|
|
189
189
|
defaultModels: ["auto"],
|
|
190
190
|
defaultModel: "auto",
|
|
191
191
|
sessionResume: null,
|
|
@@ -95,9 +95,14 @@ function normalizeAcpUpdate(update, state) {
|
|
|
95
95
|
|
|
96
96
|
if (type === "tool_call" || type === "tool_call_update") {
|
|
97
97
|
var callId = update.toolCallId;
|
|
98
|
-
var name = toolNameForKind(update.kind, update.title);
|
|
99
|
-
if (callId
|
|
100
|
-
state.toolMeta[callId] = {
|
|
98
|
+
var name = update.name || toolNameForKind(update.kind, update.title);
|
|
99
|
+
if (callId) {
|
|
100
|
+
state.toolMeta[callId] = Object.assign({}, state.toolMeta[callId] || {}, {
|
|
101
|
+
kind: update.kind || (state.toolMeta[callId] && state.toolMeta[callId].kind),
|
|
102
|
+
name: update.name || (state.toolMeta[callId] && state.toolMeta[callId].name),
|
|
103
|
+
title: update.title || (state.toolMeta[callId] && state.toolMeta[callId].title),
|
|
104
|
+
rawInput: update.rawInput || (state.toolMeta[callId] && state.toolMeta[callId].rawInput) || {},
|
|
105
|
+
});
|
|
101
106
|
}
|
|
102
107
|
if (callId && !state.toolBlocks[callId]) {
|
|
103
108
|
state.toolBlocks[callId] = "blk_" + (++state.blockCounter);
|
|
@@ -142,6 +147,15 @@ function normalizeAcpUpdate(update, state) {
|
|
|
142
147
|
return events;
|
|
143
148
|
}
|
|
144
149
|
|
|
150
|
+
if (type === "available_commands_update") {
|
|
151
|
+
var commands = Array.isArray(update.availableCommands) ? update.availableCommands : [];
|
|
152
|
+
events.push({
|
|
153
|
+
yokeType: "commands_changed",
|
|
154
|
+
commandNames: commands.map(function(command) { return command && command.name; }).filter(Boolean),
|
|
155
|
+
});
|
|
156
|
+
return events;
|
|
157
|
+
}
|
|
158
|
+
|
|
145
159
|
events.push({
|
|
146
160
|
yokeType: "runtime_specific",
|
|
147
161
|
vendor: state.vendor,
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
// Shared ACP Query Handle
|
|
2
|
-
// -----------------------
|
|
3
|
-
// Implements the YOKE QueryHandle contract with standard ACP session methods.
|
|
4
1
|
var normalizer = require("./acp-event-normalizer");
|
|
5
2
|
var driverRuntime = require("./acp-driver-runtime");
|
|
6
3
|
var queryAbort = require("./query-abort");
|
|
4
|
+
var acpUserInput = require("./acp-user-input");
|
|
7
5
|
var uuidCounter = 0;
|
|
8
6
|
function generateUuid(vendor) {
|
|
9
7
|
uuidCounter++;
|
|
@@ -31,12 +29,11 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
31
29
|
var canUseTool = queryOpts.canUseTool || null;
|
|
32
30
|
var state = normalizer.createEventState({ vendor: vendor, contextWindow: queryOpts.contextWindow });
|
|
33
31
|
state.sessionId = queryOpts.resumeSessionId || null;
|
|
34
|
-
state.model = queryOpts.model || "auto";
|
|
32
|
+
state.model = queryOpts.model || "auto"; state.effort = queryOpts.effort || null;
|
|
35
33
|
state.done = false;
|
|
36
34
|
state.aborted = false;
|
|
37
35
|
state.loadingSession = false;
|
|
38
36
|
state.toolPolicy = "ask";
|
|
39
|
-
|
|
40
37
|
var eventBuffer = [];
|
|
41
38
|
var eventWaiting = null;
|
|
42
39
|
var iteratorDone = false;
|
|
@@ -45,7 +42,6 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
45
42
|
var messageQueueEnded = false;
|
|
46
43
|
var loopStarted = false;
|
|
47
44
|
var finishedNotified = false;
|
|
48
|
-
|
|
49
45
|
function driverContext(extra) {
|
|
50
46
|
return Object.assign({
|
|
51
47
|
vendor: vendor,
|
|
@@ -55,7 +51,6 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
55
51
|
queryOpts: queryOpts,
|
|
56
52
|
}, extra || {});
|
|
57
53
|
}
|
|
58
|
-
|
|
59
54
|
function notifyFinished() {
|
|
60
55
|
if (finishedNotified) return;
|
|
61
56
|
finishedNotified = true;
|
|
@@ -169,7 +164,7 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
169
164
|
return;
|
|
170
165
|
}
|
|
171
166
|
|
|
172
|
-
var toolName = normalizer.toolNameForKind(toolCall.kind || meta.kind, toolCall.title || meta.title);
|
|
167
|
+
var toolName = toolCall.name || meta.name || normalizer.toolNameForKind(toolCall.kind || meta.kind, toolCall.title || meta.title);
|
|
173
168
|
var input = toolCall.rawInput || meta.rawInput || { title: toolCall.title || meta.title };
|
|
174
169
|
var permission = driverRuntime.call(driver, "mapPermissionRequest", driverContext({
|
|
175
170
|
message: msg,
|
|
@@ -186,6 +181,10 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
186
181
|
}
|
|
187
182
|
|
|
188
183
|
function handleServerEvent(msg) {
|
|
184
|
+
if (msg.method === "elicitation/create") {
|
|
185
|
+
acpUserInput.handleElicitation(acp, msg, queryOpts.onUserInputRequest, queryOpts.onElicitation, cancellation.signal, vendor, driver && driver.displayName);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
189
188
|
if (msg.method === "session/request_permission") {
|
|
190
189
|
handlePermission(msg);
|
|
191
190
|
return;
|
|
@@ -386,6 +385,7 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
386
385
|
await cancellation.wait(driverRuntime.callAsync(driver, "afterSessionOpen", driverContext({ sessionResult: sessionResult }), function() {}));
|
|
387
386
|
await cancellation.wait(ensureSafePermissionMode());
|
|
388
387
|
if (state.model && state.model !== "auto") await setModel(state.model);
|
|
388
|
+
if (state.effort) await setEffort(state.effort);
|
|
389
389
|
if (queryOpts.mode) await setConfig("mode", "mode", queryOpts.mode);
|
|
390
390
|
|
|
391
391
|
while (!isCancelled()) {
|
|
@@ -451,7 +451,7 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
451
451
|
return setModel(model).then(function() { state.model = model; });
|
|
452
452
|
},
|
|
453
453
|
setEffort: function(effort) {
|
|
454
|
-
return setEffort(effort);
|
|
454
|
+
return setEffort(effort).then(function() { state.effort = effort; });
|
|
455
455
|
},
|
|
456
456
|
setToolPolicy: function(policy) {
|
|
457
457
|
state.toolPolicy = policy === "allow-all" ? "allow-all" : "ask";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// ACP elicitation/create mapping (form mode only; Clay advertises
|
|
2
|
+
// clientCapabilities.elicitation.form and never url).
|
|
3
|
+
var userInput = require("./user-input");
|
|
4
|
+
var elicitationSchema = require("./elicitation-schema");
|
|
5
|
+
|
|
6
|
+
var INVALID_PARAMS = -32602;
|
|
7
|
+
|
|
8
|
+
// Maps a host elicitation result to an ACP response. Legacy "reject" is an
|
|
9
|
+
// explicit refusal; anything that is not accept or decline is a dismissal.
|
|
10
|
+
function protocolResponse(response, schema) {
|
|
11
|
+
var action = response && response.action;
|
|
12
|
+
if (action === "accept") return { action: "accept", content: elicitationSchema.validateContent(response.content || {}, schema) };
|
|
13
|
+
if (action === "decline" || action === "reject") return { action: "decline" };
|
|
14
|
+
return { action: "cancel" };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function handleElicitation(acp, msg, handler, elicitationHandler, signal, provider, displayName) {
|
|
18
|
+
var params = msg.params || {};
|
|
19
|
+
var mode = params.mode || "form";
|
|
20
|
+
if (mode !== "form") {
|
|
21
|
+
acp.respondError(msg.id, INVALID_PARAMS, "Unsupported elicitation mode: " + mode + ". Clay advertises form mode only.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (typeof handler !== "function" && typeof elicitationHandler !== "function") {
|
|
25
|
+
acp.respond(msg.id, { action: "cancel" });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
var request = {
|
|
29
|
+
serverName: params.serverName || displayName || "Agent",
|
|
30
|
+
message: params.message || "",
|
|
31
|
+
mode: "form",
|
|
32
|
+
requestedSchema: params.requestedSchema || null,
|
|
33
|
+
};
|
|
34
|
+
var settled = false;
|
|
35
|
+
var abortHandler = null;
|
|
36
|
+
function finish(response, error) {
|
|
37
|
+
if (settled) return;
|
|
38
|
+
settled = true;
|
|
39
|
+
if (signal && abortHandler && typeof signal.removeEventListener === "function") signal.removeEventListener("abort", abortHandler);
|
|
40
|
+
if (error) acp.respondError(msg.id, INVALID_PARAMS, error.message);
|
|
41
|
+
else acp.respond(msg.id, response);
|
|
42
|
+
}
|
|
43
|
+
if (typeof handler !== "function") {
|
|
44
|
+
abortHandler = function () { finish({ action: "cancel" }); };
|
|
45
|
+
if (signal && signal.aborted) return abortHandler();
|
|
46
|
+
if (signal && typeof signal.addEventListener === "function") signal.addEventListener("abort", abortHandler, { once: true });
|
|
47
|
+
Promise.resolve().then(function () {
|
|
48
|
+
return elicitationHandler(request, { signal: signal });
|
|
49
|
+
}).then(function (response) {
|
|
50
|
+
finish(protocolResponse(response, request.requestedSchema));
|
|
51
|
+
}).catch(function (error) {
|
|
52
|
+
finish(null, error);
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
userInput.dispatchElicitation(handler, request, {
|
|
57
|
+
requestId: String(msg.id),
|
|
58
|
+
signal: signal,
|
|
59
|
+
source: "acp_elicitation",
|
|
60
|
+
native: true,
|
|
61
|
+
provider: provider,
|
|
62
|
+
diagnostics: { elicitation: Object.assign({}, params, { serverName: request.serverName, mode: "form" }) },
|
|
63
|
+
}).then(function (result) {
|
|
64
|
+
finish(userInput.elicitationResponse(result, request.requestedSchema));
|
|
65
|
+
}).catch(function (error) {
|
|
66
|
+
finish(null, error);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { handleElicitation: handleElicitation, protocolResponse: protocolResponse };
|
package/lib/yoke/adapters/acp.js
CHANGED
|
@@ -16,15 +16,24 @@ function modelValues(configOptions) {
|
|
|
16
16
|
var option = options[i];
|
|
17
17
|
if (option.category !== "model" && option.id !== "model") continue;
|
|
18
18
|
var values = Array.isArray(option.options) ? option.options : [];
|
|
19
|
-
|
|
20
|
-
var value = values[j] && values[j].value;
|
|
21
|
-
if (value && models.indexOf(value) === -1) models.push(value);
|
|
22
|
-
}
|
|
19
|
+
collectModelValues(values, models);
|
|
23
20
|
break;
|
|
24
21
|
}
|
|
25
22
|
return models;
|
|
26
23
|
}
|
|
27
24
|
|
|
25
|
+
function collectModelValues(options, models) {
|
|
26
|
+
for (var i = 0; i < options.length; i++) {
|
|
27
|
+
var option = options[i] || {};
|
|
28
|
+
if (Array.isArray(option.options)) {
|
|
29
|
+
collectModelValues(option.options, models);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
var value = option.value || option.id;
|
|
33
|
+
if (value && models.indexOf(value) === -1) models.push(value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
28
37
|
function createAcpAdapter(vendor, opts) {
|
|
29
38
|
opts = opts || {};
|
|
30
39
|
var driver = opts._profile || opts._driver || profiles.getAcpAgentDriver(vendor);
|
|
@@ -87,7 +96,7 @@ function createAcpAdapter(vendor, opts) {
|
|
|
87
96
|
rewind: false,
|
|
88
97
|
sessionResume: canResumeSession() || canLoadSession(),
|
|
89
98
|
promptSuggestions: false,
|
|
90
|
-
elicitation:
|
|
99
|
+
elicitation: true,
|
|
91
100
|
fileCheckpointing: false,
|
|
92
101
|
contextCompacting: false,
|
|
93
102
|
skillSharing: true,
|
|
@@ -130,6 +139,10 @@ function createAcpAdapter(vendor, opts) {
|
|
|
130
139
|
|
|
131
140
|
var adapter = {
|
|
132
141
|
vendor: vendor,
|
|
142
|
+
// Structured questions use the ask_user_questions fallback tool: shared ACP
|
|
143
|
+
// agents have no standard native question method. Agent-initiated forms
|
|
144
|
+
// still arrive natively through elicitation/create.
|
|
145
|
+
userInputCapability: { mode: "fallback", native: false, nativeElicitation: true, source: "acp_elicitation" },
|
|
133
146
|
|
|
134
147
|
init: function(initOpts) {
|
|
135
148
|
if (shuttingDown) return Promise.reject(new Error(driver.displayName + " adapter is shutting down"));
|
|
@@ -177,6 +190,7 @@ function createAcpAdapter(vendor, opts) {
|
|
|
177
190
|
clientInfo: { name: "clay", version: "1.0.0" },
|
|
178
191
|
clientCapabilities: {
|
|
179
192
|
fs: { readTextFile: false, writeTextFile: false },
|
|
193
|
+
elicitation: { form: {} },
|
|
180
194
|
session: { configOptions: { boolean: {} } },
|
|
181
195
|
},
|
|
182
196
|
});
|
|
@@ -230,11 +244,14 @@ function createAcpAdapter(vendor, opts) {
|
|
|
230
244
|
driver: driver,
|
|
231
245
|
cwd: queryOpts.cwd || cwd,
|
|
232
246
|
model: queryOpts.model || defaultModel,
|
|
247
|
+
effort: queryOpts.effort || null,
|
|
233
248
|
mode: queryOpts.mode || null,
|
|
234
249
|
systemPrompt: queryOpts.systemPrompt || "",
|
|
235
250
|
appendSystemPrompt: [queryOpts.appendSystemPrompt, skillIndex].filter(function(part) { return !!part; }).join("\n\n"),
|
|
236
251
|
abortController: controller,
|
|
237
252
|
canUseTool: queryOpts.canUseTool || null,
|
|
253
|
+
onUserInputRequest: queryOpts.onUserInputRequest || null,
|
|
254
|
+
onElicitation: queryOpts.onElicitation || null,
|
|
238
255
|
resumeSessionId: queryOpts.resumeSessionId || null,
|
|
239
256
|
canLoadSession: canLoadSession(),
|
|
240
257
|
canResumeSession: canResumeSession(),
|
|
@@ -74,6 +74,23 @@ function toolName(name) {
|
|
|
74
74
|
return name || "Tool";
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
var USAGE_FIELDS = ["input_tokens", "output_tokens", "cache_read_tokens"];
|
|
78
|
+
var ZERO_USAGE = { input_tokens: 0, output_tokens: 0, cache_read_tokens: 0 };
|
|
79
|
+
|
|
80
|
+
// Result counters are cumulative for the conversation. A per-turn delta is
|
|
81
|
+
// trustworthy only against a known baseline that has not gone backwards.
|
|
82
|
+
function usageDelta(current, baseline) {
|
|
83
|
+
if (!current || !baseline) return null;
|
|
84
|
+
var result = {};
|
|
85
|
+
for (var i = 0; i < USAGE_FIELDS.length; i++) {
|
|
86
|
+
var field = USAGE_FIELDS[i];
|
|
87
|
+
var delta = (current[field] || 0) - (baseline[field] || 0);
|
|
88
|
+
if (delta < 0) return null;
|
|
89
|
+
result[field] = delta;
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
|
|
77
94
|
function createAntigravityQueryHandle(binaryPath, queryOpts, onFinished) {
|
|
78
95
|
var processHandle = null;
|
|
79
96
|
var removeAbort = function () {};
|
|
@@ -97,6 +114,14 @@ function createAntigravityQueryHandle(binaryPath, queryOpts, onFinished) {
|
|
|
97
114
|
var textBlockId = null;
|
|
98
115
|
var toolBlocks = {};
|
|
99
116
|
var latestUsage = null;
|
|
117
|
+
// Trusted cumulative counters from the previous result. A fresh conversation
|
|
118
|
+
// starts at zero; a resumed one has no trustworthy baseline until its first
|
|
119
|
+
// result, and a result without counters invalidates the baseline again.
|
|
120
|
+
var usageBaseline = queryOpts.resumeSessionId ? null : ZERO_USAGE;
|
|
121
|
+
var durationBaseline = queryOpts.resumeSessionId ? null : 0;
|
|
122
|
+
var turnUsage = null;
|
|
123
|
+
var turnDurationSeconds = null;
|
|
124
|
+
var countedStepIndexes = {};
|
|
100
125
|
var firstMessage = true;
|
|
101
126
|
|
|
102
127
|
function notifyFinished() {
|
|
@@ -146,6 +171,9 @@ function createAntigravityQueryHandle(binaryPath, queryOpts, onFinished) {
|
|
|
146
171
|
activeTurn = true;
|
|
147
172
|
textBlockId = null;
|
|
148
173
|
toolBlocks = {};
|
|
174
|
+
turnUsage = null;
|
|
175
|
+
turnDurationSeconds = null;
|
|
176
|
+
countedStepIndexes = {};
|
|
149
177
|
pushEvent({ yokeType: "turn_start", messageType: "user" });
|
|
150
178
|
processHandle.stdin.write(JSON.stringify({
|
|
151
179
|
event: "user",
|
|
@@ -156,7 +184,17 @@ function createAntigravityQueryHandle(binaryPath, queryOpts, onFinished) {
|
|
|
156
184
|
function finishTurn(result) {
|
|
157
185
|
result = result || {};
|
|
158
186
|
if (result.conversation_id) sessionId = result.conversation_id;
|
|
159
|
-
|
|
187
|
+
// Prefer the cumulative counter delta; otherwise fall back to this turn's
|
|
188
|
+
// completed-step totals, or leave the turn unknown.
|
|
189
|
+
var resultUsage = result.usage || null;
|
|
190
|
+
var emittedUsage = usageDelta(resultUsage, usageBaseline) || turnUsage;
|
|
191
|
+
usageBaseline = resultUsage;
|
|
192
|
+
if (resultUsage) latestUsage = resultUsage;
|
|
193
|
+
var totalSeconds = typeof result.duration_seconds === "number" ? result.duration_seconds : null;
|
|
194
|
+
var durationSeconds = totalSeconds != null && durationBaseline != null && totalSeconds >= durationBaseline
|
|
195
|
+
? totalSeconds - durationBaseline
|
|
196
|
+
: turnDurationSeconds;
|
|
197
|
+
durationBaseline = totalSeconds;
|
|
160
198
|
var status = result.status || "SUCCESS";
|
|
161
199
|
if (status !== "SUCCESS") {
|
|
162
200
|
if (status === "CANCELED" || status === "INTERRUPTED") {
|
|
@@ -172,11 +210,11 @@ function createAntigravityQueryHandle(binaryPath, queryOpts, onFinished) {
|
|
|
172
210
|
yokeType: "result",
|
|
173
211
|
messageType: "assistant",
|
|
174
212
|
cost: null,
|
|
175
|
-
duration: typeof
|
|
176
|
-
usage:
|
|
177
|
-
input_tokens:
|
|
178
|
-
output_tokens:
|
|
179
|
-
cache_read_input_tokens:
|
|
213
|
+
duration: typeof durationSeconds === "number" ? durationSeconds * 1000 : null,
|
|
214
|
+
usage: emittedUsage ? {
|
|
215
|
+
input_tokens: emittedUsage.input_tokens || 0,
|
|
216
|
+
output_tokens: emittedUsage.output_tokens || 0,
|
|
217
|
+
cache_read_input_tokens: emittedUsage.cache_read_tokens || 0,
|
|
180
218
|
cache_creation_input_tokens: 0,
|
|
181
219
|
} : null,
|
|
182
220
|
sessionId: sessionId,
|
|
@@ -189,6 +227,17 @@ function createAntigravityQueryHandle(binaryPath, queryOpts, onFinished) {
|
|
|
189
227
|
|
|
190
228
|
function handleStep(step) {
|
|
191
229
|
if (!step) return;
|
|
230
|
+
var stepKey = step.step_index == null ? null : String(step.step_index);
|
|
231
|
+
if (step.state === "DONE" && (stepKey == null || !countedStepIndexes[stepKey])) {
|
|
232
|
+
if (stepKey != null) countedStepIndexes[stepKey] = true;
|
|
233
|
+
if (step.usage) {
|
|
234
|
+
if (!turnUsage) turnUsage = {};
|
|
235
|
+
USAGE_FIELDS.forEach(function(field) {
|
|
236
|
+
turnUsage[field] = (turnUsage[field] || 0) + (step.usage[field] || 0);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (typeof step.duration_seconds === "number") turnDurationSeconds = (turnDurationSeconds || 0) + step.duration_seconds;
|
|
240
|
+
}
|
|
192
241
|
if (step.step_type === "agent_response") {
|
|
193
242
|
if (!textBlockId) {
|
|
194
243
|
textBlockId = "agy_blk_" + (++blockCounter);
|
|
@@ -370,7 +370,7 @@ function onElicitation(request, opts) {
|
|
|
370
370
|
if (opts.signal) {
|
|
371
371
|
opts.signal.addEventListener("abort", function() {
|
|
372
372
|
delete pendingElicitations[requestId];
|
|
373
|
-
resolve({ action: "
|
|
373
|
+
resolve({ action: "cancel" });
|
|
374
374
|
});
|
|
375
375
|
}
|
|
376
376
|
});
|