Support sudo password flow for shell commands
This commit is contained in:
@@ -145,7 +145,6 @@ function formatToolCommand(tool, args) {
|
||||
if (tool === "file_write") return `$ file_write ${args.path || ""}`.trim();
|
||||
if (tool === "list_dir") return `$ list_dir ${args.path || "."}`.trim();
|
||||
if (tool === "search_files") return `$ search_files ${args.query || ""}`.trim();
|
||||
if (tool === "os_update_check") return "$ os_update_check";
|
||||
return `$ ${tool || "tool"}`;
|
||||
}
|
||||
|
||||
@@ -213,6 +212,41 @@ function appendApprovalTerminal(article, eventPayload) {
|
||||
terminal?.append(createInlineApprovalActions(payload.approval_id, command));
|
||||
}
|
||||
|
||||
function appendPasswordPrompt(article, eventPayload) {
|
||||
const payload = eventPayload.payload || eventPayload;
|
||||
const terminal = article?.querySelector(".tool-terminal.is-error") || article?.querySelector(".tool-terminal");
|
||||
if (!terminal) return;
|
||||
const body = terminal.querySelector(".tool-terminal-body");
|
||||
const status = terminal.querySelector(".tool-terminal-status");
|
||||
terminal.classList.add("is-waiting");
|
||||
terminal.dataset.approvalId = payload.approval_id || terminal.dataset.approvalId || "";
|
||||
terminal.dataset.taskId = eventPayload.task_id || terminal.dataset.taskId || "";
|
||||
if (status) status.textContent = "password";
|
||||
if (body) {
|
||||
body.textContent = [
|
||||
body.textContent.trim(),
|
||||
"",
|
||||
payload.reason || "Sudo password is required to continue.",
|
||||
].filter(Boolean).join("\n");
|
||||
}
|
||||
if (!terminal.querySelector(".tool-password-form")) terminal.append(createPasswordForm());
|
||||
}
|
||||
|
||||
function createPasswordForm() {
|
||||
const form = document.createElement("form");
|
||||
form.className = "tool-password-form";
|
||||
const input = document.createElement("input");
|
||||
input.type = "password";
|
||||
input.autocomplete = "current-password";
|
||||
input.placeholder = "Sudo password";
|
||||
input.required = true;
|
||||
const button = document.createElement("button");
|
||||
button.type = "submit";
|
||||
button.textContent = "Continue";
|
||||
form.append(input, button);
|
||||
return form;
|
||||
}
|
||||
|
||||
function createInlineApprovalActions(approvalId, command) {
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "tool-approval-actions";
|
||||
@@ -267,6 +301,22 @@ async function resolveInlineApproval(button) {
|
||||
}
|
||||
}
|
||||
|
||||
async function submitToolPassword(form) {
|
||||
const terminal = form.closest(".tool-terminal");
|
||||
const article = terminal?.closest(".message");
|
||||
const taskId = terminal?.dataset.taskId;
|
||||
const approvalId = terminal?.dataset.approvalId;
|
||||
const input = form.querySelector("input");
|
||||
const password = input?.value || "";
|
||||
if (!terminal || !article || !taskId || !approvalId || !password) return;
|
||||
form.querySelectorAll("input, button").forEach((item) => {
|
||||
item.disabled = true;
|
||||
});
|
||||
await continueAfterPassword(article, taskId, approvalId, password);
|
||||
input.value = "";
|
||||
form.remove();
|
||||
}
|
||||
|
||||
function humanApprovalDecision(action) {
|
||||
if (action === "allow_once") return "разрешено один раз";
|
||||
if (action === "allow_forever") return "разрешено навсегда";
|
||||
@@ -423,6 +473,11 @@ async function handleAssistantStreamEvent(pending, name, data, context) {
|
||||
appendApprovalTerminal(pending, data);
|
||||
return;
|
||||
}
|
||||
if (name === "tool_password_requested") {
|
||||
pending.querySelector(".message-meta span").textContent = "password";
|
||||
appendPasswordPrompt(pending, data);
|
||||
return;
|
||||
}
|
||||
if (name === "content_delta") {
|
||||
if (!context.contentStarted) {
|
||||
context.contentStarted = true;
|
||||
@@ -473,6 +528,30 @@ async function continueAfterInlineApproval(article, taskId, approvalId) {
|
||||
}
|
||||
}
|
||||
|
||||
async function continueAfterPassword(article, taskId, approvalId, password) {
|
||||
if (!article || state.running) return;
|
||||
state.running = true;
|
||||
document.querySelector("#run").disabled = true;
|
||||
setStatus("#task-status", taskId, "warn");
|
||||
const context = {taskId, contentStarted: false};
|
||||
try {
|
||||
await streamSse(
|
||||
`/v1/tasks/${encodeURIComponent(taskId)}/password/stream`,
|
||||
{approval_id: approvalId, password},
|
||||
async ({name, data}) => handleAssistantStreamEvent(article, name, data, context),
|
||||
);
|
||||
} catch (error) {
|
||||
setMessagePending(article, error.message);
|
||||
article.querySelector(".message-meta span").textContent = "failed";
|
||||
setStatus("#task-status", "failed", "bad");
|
||||
await refreshEvents(taskId);
|
||||
} finally {
|
||||
state.running = false;
|
||||
document.querySelector("#run").disabled = false;
|
||||
document.querySelector("#message")?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshConversations() {
|
||||
const list = document.querySelector("#conversation-list");
|
||||
if (!list) return;
|
||||
@@ -611,6 +690,12 @@ function bindChat() {
|
||||
const button = event.target.closest(".message-reasoning-toggle");
|
||||
if (button) toggleInlineReasoning(button);
|
||||
});
|
||||
document.querySelector("#messages")?.addEventListener("submit", (event) => {
|
||||
const form = event.target.closest(".tool-password-form");
|
||||
if (!form) return;
|
||||
event.preventDefault();
|
||||
submitToolPassword(form).catch(console.error);
|
||||
});
|
||||
document.querySelector("#debug")?.addEventListener("change", (event) => {
|
||||
document.querySelector("#debug-panel").hidden = !event.target.checked;
|
||||
});
|
||||
|
||||
@@ -635,6 +635,38 @@ dd {
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.tool-password-form {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 8px;
|
||||
padding: 10px 12px 12px;
|
||||
border-top: 1px solid #1e293b;
|
||||
background: #111827;
|
||||
}
|
||||
|
||||
.tool-password-form input {
|
||||
min-width: 0;
|
||||
border-color: #334155;
|
||||
background: #0f172a;
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
.tool-password-form button {
|
||||
border: 0;
|
||||
border-radius: 7px;
|
||||
padding: 8px 10px;
|
||||
background: #2563eb;
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.tool-password-form input:disabled,
|
||||
.tool-password-form button:disabled {
|
||||
cursor: wait;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.message-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user