Skip to content

Commit 855cc4b

Browse files
fix: 修复 ask user question 的工具调用弹窗
1 parent 495ce88 commit 855cc4b

5 files changed

Lines changed: 348 additions & 8 deletions

File tree

packages/remote-control-server/src/services/transport.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export function normalizePayload(type: string, payload: unknown): Record<string,
6161
if (p.request_id) normalized.request_id = p.request_id;
6262
if (p.request) normalized.request = p.request;
6363
if (p.approved !== undefined) normalized.approved = p.approved;
64+
if (p.updated_input) normalized.updated_input = p.updated_input;
6465

6566
// Preserve message field for backward compat
6667
if (p.message) normalized.message = p.message;

packages/remote-control-server/src/transport/ws-handler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,14 @@ function toSDKMessage(event: SessionEvent): string {
4343
if (existingResponse) {
4444
msg = { type: "control_response", response: existingResponse };
4545
} else {
46+
const updatedInput = payload?.updated_input as Record<string, unknown> | undefined;
4647
msg = {
4748
type: "control_response",
4849
response: {
4950
subtype: approved ? "success" : "error",
5051
request_id: payload?.request_id ?? "",
5152
...(approved
52-
? { response: { behavior: "allow" as const } }
53+
? { response: { behavior: "allow" as const, ...(updatedInput ? { updatedInput } : {}) } }
5354
: { error: "Permission denied by user", response: { behavior: "deny" as const } }),
5455
},
5556
};

packages/remote-control-server/web/app.js

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,8 +273,99 @@ window._rejectPerm = async function (requestId, btn) {
273273
} catch (err) { alert("Failed to reject: " + err.message); btn.disabled = false; }
274274
};
275275

276+
// ============================================================
277+
// AskUserQuestion interactions
278+
// ============================================================
279+
280+
window._selectOption = function (btn, qIdx, oIdx, multiSelect) {
281+
const panel = btn.closest(".ask-panel");
282+
if (!panel) return;
283+
if (!panel._answers) panel._answers = {};
284+
285+
if (multiSelect) {
286+
// Toggle multi-select
287+
btn.classList.toggle("selected");
288+
if (!panel._answers[qIdx]) panel._answers[qIdx] = [];
289+
const arr = panel._answers[qIdx];
290+
const pos = arr.indexOf(oIdx);
291+
if (pos >= 0) arr.splice(pos, 1);
292+
else arr.push(oIdx);
293+
} else {
294+
// Single select — deselect siblings
295+
const siblings = panel.querySelectorAll(`.ask-option[data-qidx="${qIdx}"]`);
296+
siblings.forEach((s) => s.classList.remove("selected"));
297+
btn.classList.add("selected");
298+
panel._answers[qIdx] = oIdx;
299+
}
300+
};
301+
302+
window._submitOther = function (btn, qIdx) {
303+
const row = btn.closest(".ask-other-row");
304+
const input = row.querySelector(".ask-other-input");
305+
const text = input.value.trim();
306+
if (!text) return;
307+
const panel = btn.closest(".ask-panel");
308+
if (!panel) return;
309+
if (!panel._answers) panel._answers = {};
310+
panel._answers[qIdx] = text;
311+
// Deselect any option buttons
312+
panel.querySelectorAll(`.ask-option[data-qidx="${qIdx}"]`).forEach((s) => s.classList.remove("selected"));
313+
input.value = "";
314+
btn.textContent = "Sent!";
315+
setTimeout(() => { btn.textContent = "Send"; }, 1000);
316+
};
317+
318+
window._switchAskTab = function (btn, idx) {
319+
const panel = btn.closest(".ask-panel");
320+
if (!panel) return;
321+
panel.querySelectorAll(".ask-tab").forEach((t) => t.classList.remove("active"));
322+
panel.querySelectorAll(".ask-tab-page").forEach((p) => p.classList.remove("active"));
323+
btn.classList.add("active");
324+
const page = panel.querySelector(`.ask-tab-page[data-tab="${idx}"]`);
325+
if (page) page.classList.add("active");
326+
const total = panel.querySelectorAll(".ask-tab").length;
327+
const prog = panel.querySelector(".ask-progress");
328+
if (prog) prog.textContent = `${idx + 1} / ${total}`;
329+
};
330+
331+
window._submitAnswers = async function (requestId, btn) {
332+
btn.disabled = true;
333+
const panel = btn.closest(".ask-panel");
334+
const rawAnswers = panel?._answers || {};
335+
const questions = panel?._questions || [];
336+
337+
// Build updatedInput: merge original input with user's answers
338+
const answers = {};
339+
for (const [qIdx, val] of Object.entries(rawAnswers)) {
340+
const q = questions[parseInt(qIdx)];
341+
if (!q) continue;
342+
if (typeof val === "string") {
343+
// "Other" free-text answer
344+
answers[qIdx] = val;
345+
} else if (typeof val === "number") {
346+
// Selected option index — use label text
347+
const opt = q.options?.[val];
348+
answers[qIdx] = opt?.label || String(val);
349+
} else if (Array.isArray(val)) {
350+
// Multi-select — join labels
351+
answers[qIdx] = val.map((i) => q.options?.[i]?.label || String(i));
352+
}
353+
}
354+
355+
try {
356+
await apiSendControl(currentSessionId, {
357+
type: "permission_response",
358+
approved: true,
359+
request_id: requestId,
360+
updated_input: { questions, answers },
361+
});
362+
removePermissionPrompt(btn);
363+
showLoading();
364+
} catch (err) { alert("Failed to submit: " + err.message); btn.disabled = false; }
365+
};
366+
276367
function removePermissionPrompt(btn) {
277-
const prompt = btn.closest(".permission-prompt");
368+
const prompt = btn.closest(".permission-prompt, .ask-panel");
278369
if (prompt) prompt.remove();
279370
const area = document.getElementById("permission-area");
280371
if (area && area.children.length === 0) area.classList.add("hidden");

packages/remote-control-server/web/messages.css

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,154 @@
151151
border: 1px solid var(--border-light);
152152
}
153153
.permission-prompt .perm-actions { display: flex; gap: 10px; }
154+
.permission-prompt .perm-desc {
155+
font-size: 0.88rem;
156+
color: var(--text-secondary);
157+
margin-bottom: 10px;
158+
line-height: 1.5;
159+
}
160+
.permission-prompt .perm-tool-name {
161+
font-size: 0.82rem;
162+
color: var(--text-primary);
163+
margin-bottom: 6px;
164+
}
165+
166+
/* === AskUserQuestion Panel === */
167+
.ask-panel {
168+
background: var(--bg-card);
169+
border: 1.5px solid var(--accent);
170+
border-radius: var(--radius);
171+
padding: 20px 24px;
172+
margin-top: 8px;
173+
max-width: 95%;
174+
align-self: flex-start;
175+
box-shadow: 0 2px 12px rgba(217, 119, 87, 0.15);
176+
}
177+
.ask-panel .ask-title {
178+
font-family: var(--font-display);
179+
font-weight: 600;
180+
font-size: 1rem;
181+
margin-bottom: 16px;
182+
color: var(--text-primary);
183+
}
184+
.ask-question {
185+
margin-bottom: 18px;
186+
padding-bottom: 14px;
187+
border-bottom: 1px solid var(--border-light);
188+
}
189+
.ask-question:last-of-type { border-bottom: none; margin-bottom: 12px; }
190+
.ask-question-text {
191+
font-size: 0.92rem;
192+
font-weight: 500;
193+
color: var(--text-primary);
194+
margin-bottom: 8px;
195+
line-height: 1.5;
196+
}
197+
.ask-header {
198+
font-size: 0.78rem;
199+
font-weight: 600;
200+
color: var(--text-secondary);
201+
text-transform: uppercase;
202+
letter-spacing: 0.04em;
203+
margin-bottom: 8px;
204+
}
205+
.ask-options { display: flex; flex-direction: column; gap: 6px; }
206+
.ask-option {
207+
display: flex;
208+
flex-direction: column;
209+
align-items: flex-start;
210+
padding: 10px 14px;
211+
border: 1.5px solid var(--border);
212+
border-radius: var(--radius-sm);
213+
background: var(--bg-primary);
214+
cursor: pointer;
215+
transition: all var(--transition-fast);
216+
text-align: left;
217+
font-size: 0.88rem;
218+
color: var(--text-primary);
219+
}
220+
.ask-option:hover {
221+
border-color: var(--accent);
222+
background: rgba(217, 119, 87, 0.04);
223+
}
224+
.ask-option.selected {
225+
border-color: var(--accent);
226+
background: rgba(217, 119, 87, 0.1);
227+
box-shadow: 0 0 0 1px var(--accent);
228+
}
229+
.ask-option-label { font-weight: 500; }
230+
.ask-option-desc {
231+
font-size: 0.8rem;
232+
color: var(--text-secondary);
233+
margin-top: 2px;
234+
}
235+
.ask-other-row {
236+
display: flex;
237+
gap: 6px;
238+
margin-top: 6px;
239+
}
240+
.ask-other-input {
241+
flex: 1;
242+
padding: 8px 12px;
243+
border: 1.5px solid var(--border);
244+
border-radius: var(--radius-sm);
245+
background: var(--bg-primary);
246+
font-size: 0.85rem;
247+
color: var(--text-primary);
248+
outline: none;
249+
transition: border-color var(--transition-fast);
250+
}
251+
.ask-other-input:focus { border-color: var(--accent); }
252+
.ask-other-btn {
253+
padding: 8px 14px;
254+
border: 1.5px solid var(--border);
255+
border-radius: var(--radius-sm);
256+
background: var(--bg-primary);
257+
font-size: 0.85rem;
258+
cursor: pointer;
259+
color: var(--text-primary);
260+
transition: all var(--transition-fast);
261+
}
262+
.ask-other-btn:hover { border-color: var(--accent); }
263+
.ask-actions { display: flex; gap: 10px; margin-top: 8px; }
264+
.ask-tabs {
265+
display: flex;
266+
gap: 0;
267+
border-bottom: 1.5px solid var(--border);
268+
margin-bottom: 14px;
269+
}
270+
.ask-tab {
271+
padding: 8px 16px;
272+
border: none;
273+
background: none;
274+
cursor: pointer;
275+
font-size: 0.85rem;
276+
font-weight: 500;
277+
color: var(--text-secondary);
278+
border-bottom: 2px solid transparent;
279+
margin-bottom: -1.5px;
280+
transition: all var(--transition-fast);
281+
}
282+
.ask-tab:hover { color: var(--text-primary); }
283+
.ask-tab.active {
284+
color: var(--accent);
285+
border-bottom-color: var(--accent);
286+
}
287+
.ask-tab-page { display: none; }
288+
.ask-tab-page.active { display: block; }
289+
.ask-tab-footer {
290+
display: flex;
291+
align-items: center;
292+
justify-content: space-between;
293+
margin-top: 16px;
294+
padding-top: 12px;
295+
border-top: 1px solid var(--border-light);
296+
}
297+
.ask-progress {
298+
font-size: 0.8rem;
299+
color: var(--text-muted);
300+
font-family: var(--font-mono);
301+
}
154302

155303
/* === Timestamps === */
156304
.event-time { font-size: 0.7rem; color: var(--text-muted); margin-top: 4px; }

0 commit comments

Comments
 (0)