mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-09 14:57:59 -05:00
feat: Implement user confirmation mode, request confirmation when running bash/python code in this mode (#2774)
* [feat] confirmation mode for bash actions * feat: Add modal setting for Confirmation Mode * fix: frontend tests for confirmation mode switch * fix: add missing CONFIRMATION_MODE value in SettingsModal.test.tsx * fix: update test to integrate new setting * feat: Implement user confirmation for running bash/python code * fix: don't display rejected actions * fix: linting, rename/refactor based on feedback * fix: add property only to commands, pass serialization tests * fix: package-lock.json, lint test_action_serialization.py * test: add is_confirmed to integration test outputs --------- Co-authored-by: Mislav Balunovic <mislav.balunovic@gmail.com>
This commit is contained in:
18
frontend/src/assets/confirm.tsx
Normal file
18
frontend/src/assets/confirm.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
|
||||
function ConfirmIcon(): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-5 h-5"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConfirmIcon;
|
||||
22
frontend/src/assets/reject.tsx
Normal file
22
frontend/src/assets/reject.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from "react";
|
||||
|
||||
function RejectIcon(): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default RejectIcon;
|
||||
@@ -17,6 +17,7 @@ const IgnoreTaskStateMap: { [k: string]: AgentState[] } = {
|
||||
AgentState.FINISHED,
|
||||
AgentState.REJECTED,
|
||||
AgentState.AWAITING_USER_INPUT,
|
||||
AgentState.AWAITING_USER_CONFIRMATION,
|
||||
],
|
||||
[AgentState.RUNNING]: [
|
||||
AgentState.INIT,
|
||||
@@ -25,8 +26,12 @@ const IgnoreTaskStateMap: { [k: string]: AgentState[] } = {
|
||||
AgentState.FINISHED,
|
||||
AgentState.REJECTED,
|
||||
AgentState.AWAITING_USER_INPUT,
|
||||
AgentState.AWAITING_USER_CONFIRMATION,
|
||||
],
|
||||
[AgentState.STOPPED]: [AgentState.INIT, AgentState.STOPPED],
|
||||
[AgentState.USER_CONFIRMED]: [AgentState.RUNNING],
|
||||
[AgentState.USER_REJECTED]: [AgentState.RUNNING],
|
||||
[AgentState.AWAITING_USER_CONFIRMATION]: [],
|
||||
};
|
||||
|
||||
interface ButtonProps {
|
||||
@@ -101,42 +106,44 @@ function AgentControlBar() {
|
||||
}, [curAgentState]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
{curAgentState === AgentState.PAUSED ? (
|
||||
<div className="flex justify-between items-center gap-20">
|
||||
<div className="flex items-center gap-3">
|
||||
{curAgentState === AgentState.PAUSED ? (
|
||||
<ActionButton
|
||||
isDisabled={
|
||||
isLoading ||
|
||||
IgnoreTaskStateMap[AgentState.RUNNING].includes(curAgentState)
|
||||
}
|
||||
content="Resume the agent task"
|
||||
action={AgentState.RUNNING}
|
||||
handleAction={handleAction}
|
||||
large
|
||||
>
|
||||
<PlayIcon />
|
||||
</ActionButton>
|
||||
) : (
|
||||
<ActionButton
|
||||
isDisabled={
|
||||
isLoading ||
|
||||
IgnoreTaskStateMap[AgentState.PAUSED].includes(curAgentState)
|
||||
}
|
||||
content="Pause the current task"
|
||||
action={AgentState.PAUSED}
|
||||
handleAction={handleAction}
|
||||
large
|
||||
>
|
||||
<PauseIcon />
|
||||
</ActionButton>
|
||||
)}
|
||||
<ActionButton
|
||||
isDisabled={
|
||||
isLoading ||
|
||||
IgnoreTaskStateMap[AgentState.RUNNING].includes(curAgentState)
|
||||
}
|
||||
content="Resume the agent task"
|
||||
action={AgentState.RUNNING}
|
||||
isDisabled={isLoading}
|
||||
content="Start a new task"
|
||||
action={AgentState.STOPPED}
|
||||
handleAction={handleAction}
|
||||
large
|
||||
>
|
||||
<PlayIcon />
|
||||
<ArrowIcon />
|
||||
</ActionButton>
|
||||
) : (
|
||||
<ActionButton
|
||||
isDisabled={
|
||||
isLoading ||
|
||||
IgnoreTaskStateMap[AgentState.PAUSED].includes(curAgentState)
|
||||
}
|
||||
content="Pause the current task"
|
||||
action={AgentState.PAUSED}
|
||||
handleAction={handleAction}
|
||||
large
|
||||
>
|
||||
<PauseIcon />
|
||||
</ActionButton>
|
||||
)}
|
||||
<ActionButton
|
||||
isDisabled={isLoading}
|
||||
content="Start a new task"
|
||||
action={AgentState.STOPPED}
|
||||
handleAction={handleAction}
|
||||
>
|
||||
<ArrowIcon />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,6 +58,20 @@ function AgentStatusBar() {
|
||||
message: t(I18nKey.CHAT_INTERFACE$AGENT_ERROR_MESSAGE),
|
||||
indicator: IndicatorColor.RED,
|
||||
},
|
||||
[AgentState.AWAITING_USER_CONFIRMATION]: {
|
||||
message: t(
|
||||
I18nKey.CHAT_INTERFACE$AGENT_AWAITING_USER_CONFIRMATION_MESSAGE,
|
||||
),
|
||||
indicator: IndicatorColor.ORANGE,
|
||||
},
|
||||
[AgentState.USER_CONFIRMED]: {
|
||||
message: t(I18nKey.CHAT_INTERFACE$AGENT_ACTION_USER_CONFIRMED_MESSAGE),
|
||||
indicator: IndicatorColor.GREEN,
|
||||
},
|
||||
[AgentState.USER_REJECTED]: {
|
||||
message: t(I18nKey.CHAT_INTERFACE$AGENT_ACTION_USER_REJECTED_MESSAGE),
|
||||
indicator: IndicatorColor.RED,
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: Extend the agent status, e.g.:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { screen } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { renderWithProviders } from "test-utils";
|
||||
import Chat from "./Chat";
|
||||
|
||||
const MESSAGES: Message[] = [
|
||||
@@ -13,7 +14,7 @@ HTMLElement.prototype.scrollTo = vi.fn(() => {});
|
||||
|
||||
describe("Chat", () => {
|
||||
it("should render chat messages", () => {
|
||||
render(<Chat messages={MESSAGES} />);
|
||||
renderWithProviders(<Chat messages={MESSAGES} />);
|
||||
|
||||
const messages = screen.getAllByTestId("message");
|
||||
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
import React from "react";
|
||||
import ChatMessage from "./ChatMessage";
|
||||
import AgentState from "#/types/AgentState";
|
||||
|
||||
interface ChatProps {
|
||||
messages: Message[];
|
||||
curAgentState?: AgentState;
|
||||
}
|
||||
|
||||
function Chat({ messages }: ChatProps) {
|
||||
function Chat({ messages, curAgentState }: ChatProps) {
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
{messages.map((message, index) => (
|
||||
<ChatMessage key={index} message={message} />
|
||||
<ChatMessage
|
||||
key={index}
|
||||
message={message}
|
||||
isLastMessage={messages && index === messages.length - 1}
|
||||
awaitingUserConfirmation={
|
||||
curAgentState === AgentState.AWAITING_USER_CONFIRMATION
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -123,7 +123,7 @@ function ChatInterface() {
|
||||
className="overflow-y-auto p-3"
|
||||
onScroll={(e) => onChatBodyScroll(e.currentTarget)}
|
||||
>
|
||||
<Chat messages={messages} />
|
||||
<Chat messages={messages} curAgentState={curAgentState} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -169,7 +169,10 @@ function ChatInterface() {
|
||||
</div>
|
||||
|
||||
<ChatInput
|
||||
disabled={curAgentState === AgentState.LOADING}
|
||||
disabled={
|
||||
curAgentState === AgentState.LOADING ||
|
||||
curAgentState === AgentState.AWAITING_USER_CONFIRMATION
|
||||
}
|
||||
onSendMessage={handleSendMessage}
|
||||
/>
|
||||
<FeedbackModal
|
||||
|
||||
@@ -10,14 +10,24 @@ vi.mock("#/hooks/useTyping", () => ({
|
||||
|
||||
describe("Message", () => {
|
||||
it("should render a user message", () => {
|
||||
render(<ChatMessage message={{ sender: "user", content: "Hello" }} />);
|
||||
render(
|
||||
<ChatMessage
|
||||
message={{ sender: "user", content: "Hello" }}
|
||||
isLastMessage={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("message")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("message")).toHaveClass("self-end"); // user message should be on the right side
|
||||
});
|
||||
|
||||
it("should render an assistant message", () => {
|
||||
render(<ChatMessage message={{ sender: "assistant", content: "Hi" }} />);
|
||||
render(
|
||||
<ChatMessage
|
||||
message={{ sender: "assistant", content: "Hi" }}
|
||||
isLastMessage={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("message")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("message")).not.toHaveClass("self-end"); // assistant message should be on the left side
|
||||
@@ -30,6 +40,7 @@ describe("Message", () => {
|
||||
sender: "user",
|
||||
content: "```js\nconsole.log('Hello')\n```",
|
||||
}}
|
||||
isLastMessage={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
|
||||
@@ -3,15 +3,26 @@ import Markdown from "react-markdown";
|
||||
import { FaClipboard, FaClipboardCheck } from "react-icons/fa";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Tooltip } from "@nextui-org/react";
|
||||
import AgentState from "#/types/AgentState";
|
||||
import { code } from "../markdown/code";
|
||||
import toast from "#/utils/toast";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import ConfirmIcon from "#/assets/confirm";
|
||||
import RejectIcon from "#/assets/reject";
|
||||
import { changeAgentState } from "#/services/agentStateService";
|
||||
|
||||
interface MessageProps {
|
||||
message: Message;
|
||||
isLastMessage?: boolean;
|
||||
awaitingUserConfirmation?: boolean;
|
||||
}
|
||||
|
||||
function ChatMessage({ message }: MessageProps) {
|
||||
function ChatMessage({
|
||||
message,
|
||||
isLastMessage,
|
||||
awaitingUserConfirmation,
|
||||
}: MessageProps) {
|
||||
const [isCopy, setIsCopy] = useState(false);
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
|
||||
@@ -58,6 +69,45 @@ function ChatMessage({ message }: MessageProps) {
|
||||
</button>
|
||||
)}
|
||||
<Markdown components={{ code }}>{message.content}</Markdown>
|
||||
{isLastMessage &&
|
||||
message.sender === "assistant" &&
|
||||
awaitingUserConfirmation && (
|
||||
<div className="flex justify-between items-center pt-4">
|
||||
<p>{t(I18nKey.CHAT_INTERFACE$USER_ASK_CONFIRMATION)}</p>
|
||||
<div className="flex items-center gap-3">
|
||||
<Tooltip
|
||||
content={t(I18nKey.CHAT_INTERFACE$USER_CONFIRMED)}
|
||||
closeDelay={100}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Confirm action"
|
||||
className="bg-neutral-700 rounded-full p-1 hover:bg-neutral-800"
|
||||
onClick={() => {
|
||||
changeAgentState(AgentState.USER_CONFIRMED);
|
||||
}}
|
||||
>
|
||||
<ConfirmIcon />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={t(I18nKey.CHAT_INTERFACE$USER_REJECTED)}
|
||||
closeDelay={100}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Reject action"
|
||||
className="bg-neutral-700 rounded-full p-1 hover:bg-neutral-800"
|
||||
onClick={() => {
|
||||
changeAgentState(AgentState.USER_REJECTED);
|
||||
}}
|
||||
>
|
||||
<RejectIcon />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ const onModelChangeMock = vi.fn();
|
||||
const onAgentChangeMock = vi.fn();
|
||||
const onLanguageChangeMock = vi.fn();
|
||||
const onAPIKeyChangeMock = vi.fn();
|
||||
const onConfirmationModeChangeMock = vi.fn();
|
||||
|
||||
const renderSettingsForm = (settings?: Settings) => {
|
||||
renderWithProviders(
|
||||
@@ -20,6 +21,7 @@ const renderSettingsForm = (settings?: Settings) => {
|
||||
AGENT: "agent1",
|
||||
LANGUAGE: "en",
|
||||
LLM_API_KEY: "sk-...",
|
||||
CONFIRMATION_MODE: true,
|
||||
}
|
||||
}
|
||||
models={["model1", "model2", "model3"]}
|
||||
@@ -28,6 +30,7 @@ const renderSettingsForm = (settings?: Settings) => {
|
||||
onAgentChange={onAgentChangeMock}
|
||||
onLanguageChange={onLanguageChangeMock}
|
||||
onAPIKeyChange={onAPIKeyChangeMock}
|
||||
onConfirmationModeChange={onConfirmationModeChangeMock}
|
||||
/>,
|
||||
);
|
||||
};
|
||||
@@ -40,11 +43,13 @@ describe("SettingsForm", () => {
|
||||
const agentInput = screen.getByRole("combobox", { name: "agent" });
|
||||
const languageInput = screen.getByRole("combobox", { name: "language" });
|
||||
const apiKeyInput = screen.getByTestId("apikey");
|
||||
const confirmationModeInput = screen.getByTestId("confirmationmode");
|
||||
|
||||
expect(modelInput).toHaveValue("model1");
|
||||
expect(agentInput).toHaveValue("agent1");
|
||||
expect(languageInput).toHaveValue("English");
|
||||
expect(apiKeyInput).toHaveValue("sk-...");
|
||||
expect(confirmationModeInput).toHaveAttribute("data-selected", "true");
|
||||
});
|
||||
|
||||
it("should display the existing values if it they are present", () => {
|
||||
@@ -53,6 +58,7 @@ describe("SettingsForm", () => {
|
||||
AGENT: "agent2",
|
||||
LANGUAGE: "es",
|
||||
LLM_API_KEY: "sk-...",
|
||||
CONFIRMATION_MODE: true,
|
||||
});
|
||||
|
||||
const modelInput = screen.getByRole("combobox", { name: "model" });
|
||||
@@ -72,6 +78,7 @@ describe("SettingsForm", () => {
|
||||
AGENT: "agent1",
|
||||
LANGUAGE: "en",
|
||||
LLM_API_KEY: "sk-...",
|
||||
CONFIRMATION_MODE: true,
|
||||
}}
|
||||
models={["model1", "model2", "model3"]}
|
||||
agents={["agent1", "agent2", "agent3"]}
|
||||
@@ -80,15 +87,18 @@ describe("SettingsForm", () => {
|
||||
onAgentChange={onAgentChangeMock}
|
||||
onLanguageChange={onLanguageChangeMock}
|
||||
onAPIKeyChange={onAPIKeyChangeMock}
|
||||
onConfirmationModeChange={onConfirmationModeChangeMock}
|
||||
/>,
|
||||
);
|
||||
const modelInput = screen.getByRole("combobox", { name: "model" });
|
||||
const agentInput = screen.getByRole("combobox", { name: "agent" });
|
||||
const languageInput = screen.getByRole("combobox", { name: "language" });
|
||||
const confirmationModeInput = screen.getByTestId("confirmationmode");
|
||||
|
||||
expect(modelInput).toBeDisabled();
|
||||
expect(agentInput).toBeDisabled();
|
||||
expect(languageInput).toBeDisabled();
|
||||
expect(confirmationModeInput).toHaveAttribute("data-disabled", "true");
|
||||
});
|
||||
|
||||
describe("onChange handlers", () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Input, useDisclosure } from "@nextui-org/react";
|
||||
import { Input, Switch, Tooltip, useDisclosure } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaEye, FaEyeSlash } from "react-icons/fa";
|
||||
@@ -17,6 +17,7 @@ interface SettingsFormProps {
|
||||
onAPIKeyChange: (apiKey: string) => void;
|
||||
onAgentChange: (agent: string) => void;
|
||||
onLanguageChange: (language: string) => void;
|
||||
onConfirmationModeChange: (confirmationMode: boolean) => void;
|
||||
}
|
||||
|
||||
function SettingsForm({
|
||||
@@ -28,6 +29,7 @@ function SettingsForm({
|
||||
onAPIKeyChange,
|
||||
onAgentChange,
|
||||
onLanguageChange,
|
||||
onConfirmationModeChange,
|
||||
}: SettingsFormProps) {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen: isVisible, onOpenChange: onVisibleChange } = useDisclosure();
|
||||
@@ -86,6 +88,21 @@ function SettingsForm({
|
||||
tooltip={t(I18nKey.SETTINGS$LANGUAGE_TOOLTIP)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<Switch
|
||||
aria-label="confirmationmode"
|
||||
data-testid="confirmationmode"
|
||||
defaultSelected={settings.CONFIRMATION_MODE}
|
||||
onValueChange={onConfirmationModeChange}
|
||||
isDisabled={disabled}
|
||||
>
|
||||
<Tooltip
|
||||
content={t(I18nKey.SETTINGS$CONFIRMATION_MODE_TOOLTIP)}
|
||||
closeDelay={100}
|
||||
delay={500}
|
||||
>
|
||||
{t(I18nKey.SETTINGS$CONFIRMATION_MODE)}
|
||||
</Tooltip>
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,12 +27,14 @@ vi.mock("#/services/settings", async (importOriginal) => ({
|
||||
AGENT: "CodeActAgent",
|
||||
LANGUAGE: "en",
|
||||
LLM_API_KEY: "sk-...",
|
||||
CONFIRMATION_MODE: true,
|
||||
}),
|
||||
getDefaultSettings: vi.fn().mockReturnValue({
|
||||
LLM_MODEL: "gpt-4o",
|
||||
AGENT: "CodeActAgent",
|
||||
LANGUAGE: "en",
|
||||
LLM_API_KEY: "",
|
||||
CONFIRMATION_MODE: false,
|
||||
}),
|
||||
settingsAreUpToDate: vi.fn().mockReturnValue(true),
|
||||
saveSettings: vi.fn(),
|
||||
@@ -107,6 +109,7 @@ describe("SettingsModal", () => {
|
||||
AGENT: "CodeActAgent",
|
||||
LANGUAGE: "en",
|
||||
LLM_API_KEY: "sk-...",
|
||||
CONFIRMATION_MODE: true,
|
||||
};
|
||||
|
||||
it("should save the settings", async () => {
|
||||
@@ -196,7 +199,7 @@ describe("SettingsModal", () => {
|
||||
await userEvent.click(saveButton);
|
||||
});
|
||||
|
||||
expect(toastSpy).toHaveBeenCalledTimes(2);
|
||||
expect(toastSpy).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("should change the language", async () => {
|
||||
|
||||
@@ -48,7 +48,8 @@ function SettingsModal({ isOpen, onOpenChange }: SettingsProps) {
|
||||
const isRunning =
|
||||
curAgentState === AgentState.RUNNING ||
|
||||
curAgentState === AgentState.PAUSED ||
|
||||
curAgentState === AgentState.AWAITING_USER_INPUT;
|
||||
curAgentState === AgentState.AWAITING_USER_INPUT ||
|
||||
curAgentState === AgentState.AWAITING_USER_CONFIRMATION;
|
||||
setAgentIsRunning(isRunning);
|
||||
}, [curAgentState]);
|
||||
|
||||
@@ -89,6 +90,10 @@ function SettingsModal({ isOpen, onOpenChange }: SettingsProps) {
|
||||
setSettings((prev) => ({ ...prev, LLM_API_KEY: key }));
|
||||
};
|
||||
|
||||
const handleConfirmationModeChange = (confirmationMode: boolean) => {
|
||||
setSettings((prev) => ({ ...prev, CONFIRMATION_MODE: confirmationMode }));
|
||||
};
|
||||
|
||||
const handleResetSettings = () => {
|
||||
setSettings(getDefaultSettings);
|
||||
};
|
||||
@@ -170,6 +175,7 @@ function SettingsModal({ isOpen, onOpenChange }: SettingsProps) {
|
||||
onAgentChange={handleAgentChange}
|
||||
onLanguageChange={handleLanguageChange}
|
||||
onAPIKeyChange={handleAPIKeyChange}
|
||||
onConfirmationModeChange={handleConfirmationModeChange}
|
||||
/>
|
||||
)}
|
||||
</BaseModal>
|
||||
|
||||
@@ -567,6 +567,21 @@
|
||||
"de": "Agent ist auf einen Fehler gelaufen.",
|
||||
"zh-CN": "智能体遇到错误"
|
||||
},
|
||||
"CHAT_INTERFACE$AGENT_AWAITING_USER_CONFIRMATION_MESSAGE": {
|
||||
"en": "Agent is awaiting user confirmation for the pending action.",
|
||||
"de": "Agent wartet auf die Bestätigung des Benutzers für die ausstehende Aktion.",
|
||||
"zh-CN": "代理正在等待用户确认待处理的操作。"
|
||||
},
|
||||
"CHAT_INTERFACE$AGENT_ACTION_USER_CONFIRMED_MESSAGE": {
|
||||
"en": "Agent action has been confirmed!",
|
||||
"de": "Die Aktion des Agenten wurde bestätigt!",
|
||||
"zh-CN": "代理操作已确认!"
|
||||
},
|
||||
"CHAT_INTERFACE$AGENT_ACTION_USER_REJECTED_MESSAGE": {
|
||||
"en": "Agent action has been rejected!",
|
||||
"de": "Die Aktion des Agenten wurde abgelehnt!",
|
||||
"zh-CN": "代理操作已被拒绝!"
|
||||
},
|
||||
"CHAT_INTERFACE$INPUT_PLACEHOLDER": {
|
||||
"en": "Message assistant...",
|
||||
"zh-CN": "给助理发消息",
|
||||
@@ -586,6 +601,21 @@
|
||||
"zh-CN": "继续",
|
||||
"de": "Fortfahren"
|
||||
},
|
||||
"CHAT_INTERFACE$USER_ASK_CONFIRMATION": {
|
||||
"en": "Do you want to continue with this action?",
|
||||
"de": "Möchten Sie mit dieser Aktion fortfahren?",
|
||||
"zh-CN": "您要继续此操作吗?"
|
||||
},
|
||||
"CHAT_INTERFACE$USER_CONFIRMED": {
|
||||
"en": "Confirm the requested action",
|
||||
"de": "Bestätigen Sie die angeforderte Aktion",
|
||||
"zh-CN": "确认请求的操作"
|
||||
},
|
||||
"CHAT_INTERFACE$USER_REJECTED": {
|
||||
"en": "Reject the requested action",
|
||||
"de": "Lehnen Sie die angeforderte Aktion ab",
|
||||
"zh-CN": "拒绝请求的操作"
|
||||
},
|
||||
"CHAT_INTERFACE$INPUT_SEND_MESSAGE_BUTTON_CONTENT": {
|
||||
"en": "Send",
|
||||
"zh-CN": "发送",
|
||||
@@ -681,6 +711,16 @@
|
||||
"zh-TW": "輸入您的 API 金鑰。",
|
||||
"de": "Modell API Schlüssel."
|
||||
},
|
||||
"SETTINGS$CONFIRMATION_MODE": {
|
||||
"en": "Enable Confirmation Mode",
|
||||
"de": "Bestätigungsmodus aktivieren",
|
||||
"zh-CN": "启用确认模式"
|
||||
},
|
||||
"SETTINGS$CONFIRMATION_MODE_TOOLTIP": {
|
||||
"en": "Awaits for user confirmation before executing code.",
|
||||
"de": "Wartet auf die Bestätigung des Benutzers, bevor der Code ausgeführt wird.",
|
||||
"zh-CN": "在执行代码之前等待用户确认。"
|
||||
},
|
||||
"BROWSER$EMPTY_MESSAGE": {
|
||||
"en": "No page loaded.",
|
||||
"zh-CN": "页面未加载",
|
||||
|
||||
@@ -46,13 +46,23 @@ const messageActions = {
|
||||
if (message.args.thought) {
|
||||
store.dispatch(addAssistantMessage(message.args.thought));
|
||||
}
|
||||
store.dispatch(appendInput(message.args.command));
|
||||
if (
|
||||
!message.args.is_confirmed ||
|
||||
message.args.is_confirmed !== "rejected"
|
||||
) {
|
||||
store.dispatch(appendInput(message.args.command));
|
||||
}
|
||||
},
|
||||
[ActionType.RUN_IPYTHON]: (message: ActionMessage) => {
|
||||
if (message.args.thought) {
|
||||
store.dispatch(addAssistantMessage(message.args.thought));
|
||||
}
|
||||
store.dispatch(appendJupyterInput(message.args.code));
|
||||
if (
|
||||
!message.args.is_confirmed ||
|
||||
message.args.is_confirmed !== "rejected"
|
||||
) {
|
||||
store.dispatch(appendJupyterInput(message.args.code));
|
||||
}
|
||||
},
|
||||
[ActionType.ADD_TASK]: () => {
|
||||
getRootTask().then((fetchedRootTask) =>
|
||||
@@ -67,6 +77,32 @@ const messageActions = {
|
||||
};
|
||||
|
||||
export function handleActionMessage(message: ActionMessage) {
|
||||
if (
|
||||
(message.action === ActionType.RUN ||
|
||||
message.action === ActionType.RUN_IPYTHON) &&
|
||||
message.args.is_confirmed === "awaiting_confirmation"
|
||||
) {
|
||||
if (message.args.thought) {
|
||||
store.dispatch(addAssistantMessage(message.args.thought));
|
||||
}
|
||||
if (message.args.command) {
|
||||
store.dispatch(
|
||||
addAssistantMessage(
|
||||
`Running this command now: \n\`\`\`\`bash\n${message.args.command}\n\`\`\`\`\n`,
|
||||
),
|
||||
);
|
||||
} else if (message.args.code) {
|
||||
store.dispatch(
|
||||
addAssistantMessage(
|
||||
`Running this code now: \n\`\`\`\`python\n${message.args.code}\n\`\`\`\`\n`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
store.dispatch(addAssistantMessage(message.message));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.action in messageActions) {
|
||||
const actionFn =
|
||||
messageActions[message.action as keyof typeof messageActions];
|
||||
|
||||
@@ -20,6 +20,7 @@ describe("startNewSession", () => {
|
||||
AGENT: "agent_value",
|
||||
LANGUAGE: "language_value",
|
||||
LLM_API_KEY: "sk-...",
|
||||
CONFIRMATION_MODE: true,
|
||||
};
|
||||
|
||||
const event = {
|
||||
|
||||
@@ -20,7 +20,8 @@ describe("getSettings", () => {
|
||||
.mockReturnValueOnce("llm_value")
|
||||
.mockReturnValueOnce("agent_value")
|
||||
.mockReturnValueOnce("language_value")
|
||||
.mockReturnValueOnce("api_key");
|
||||
.mockReturnValueOnce("api_key")
|
||||
.mockReturnValueOnce("true");
|
||||
|
||||
const settings = getSettings();
|
||||
|
||||
@@ -29,6 +30,7 @@ describe("getSettings", () => {
|
||||
AGENT: "agent_value",
|
||||
LANGUAGE: "language_value",
|
||||
LLM_API_KEY: "api_key",
|
||||
CONFIRMATION_MODE: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,6 +48,7 @@ describe("getSettings", () => {
|
||||
AGENT: DEFAULT_SETTINGS.AGENT,
|
||||
LANGUAGE: DEFAULT_SETTINGS.LANGUAGE,
|
||||
LLM_API_KEY: "",
|
||||
CONFIRMATION_MODE: DEFAULT_SETTINGS.CONFIRMATION_MODE,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -57,6 +60,7 @@ describe("saveSettings", () => {
|
||||
AGENT: "agent_value",
|
||||
LANGUAGE: "language_value",
|
||||
LLM_API_KEY: "some_key",
|
||||
CONFIRMATION_MODE: true,
|
||||
};
|
||||
|
||||
saveSettings(settings);
|
||||
|
||||
@@ -5,13 +5,17 @@ export type Settings = {
|
||||
AGENT: string;
|
||||
LANGUAGE: string;
|
||||
LLM_API_KEY: string;
|
||||
CONFIRMATION_MODE: boolean;
|
||||
};
|
||||
|
||||
type SettingsInput = Settings[keyof Settings];
|
||||
|
||||
export const DEFAULT_SETTINGS: Settings = {
|
||||
LLM_MODEL: "gpt-4o",
|
||||
AGENT: "CodeActAgent",
|
||||
LANGUAGE: "en",
|
||||
LLM_API_KEY: "",
|
||||
CONFIRMATION_MODE: false,
|
||||
};
|
||||
|
||||
const validKeys = Object.keys(DEFAULT_SETTINGS) as (keyof Settings)[];
|
||||
@@ -51,12 +55,14 @@ export const getSettings = (): Settings => {
|
||||
const agent = localStorage.getItem("AGENT");
|
||||
const language = localStorage.getItem("LANGUAGE");
|
||||
const apiKey = localStorage.getItem("LLM_API_KEY");
|
||||
const confirmationMode = localStorage.getItem("CONFIRMATION_MODE") === "true";
|
||||
|
||||
return {
|
||||
LLM_MODEL: model || DEFAULT_SETTINGS.LLM_MODEL,
|
||||
AGENT: agent || DEFAULT_SETTINGS.AGENT,
|
||||
LANGUAGE: language || DEFAULT_SETTINGS.LANGUAGE,
|
||||
LLM_API_KEY: apiKey || DEFAULT_SETTINGS.LLM_API_KEY,
|
||||
CONFIRMATION_MODE: confirmationMode || DEFAULT_SETTINGS.CONFIRMATION_MODE,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -69,7 +75,8 @@ export const saveSettings = (settings: Partial<Settings>) => {
|
||||
const isValid = validKeys.includes(key as keyof Settings);
|
||||
const value = settings[key as keyof Settings];
|
||||
|
||||
if (isValid && value) localStorage.setItem(key, value);
|
||||
if (isValid && (value || typeof value === "boolean"))
|
||||
localStorage.setItem(key, value.toString());
|
||||
});
|
||||
localStorage.setItem("SETTINGS_VERSION", LATEST_SETTINGS_VERSION.toString());
|
||||
};
|
||||
@@ -91,11 +98,14 @@ export const getSettingsDifference = (settings: Partial<Settings>) => {
|
||||
const updatedSettings: Partial<Settings> = {};
|
||||
|
||||
Object.keys(settings).forEach((key) => {
|
||||
const typedKey = key as keyof Settings;
|
||||
if (
|
||||
validKeys.includes(key as keyof Settings) &&
|
||||
settings[key as keyof Settings] !== currentSettings[key as keyof Settings]
|
||||
validKeys.includes(typedKey) &&
|
||||
settings[typedKey] !== currentSettings[typedKey]
|
||||
) {
|
||||
updatedSettings[key as keyof Settings] = settings[key as keyof Settings];
|
||||
(updatedSettings[typedKey] as SettingsInput) = settings[
|
||||
typedKey
|
||||
] as SettingsInput;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ enum AgentState {
|
||||
FINISHED = "finished",
|
||||
REJECTED = "rejected",
|
||||
ERROR = "error",
|
||||
AWAITING_USER_CONFIRMATION = "awaiting_user_confirmation",
|
||||
USER_CONFIRMED = "user_confirmed",
|
||||
USER_REJECTED = "user_rejected",
|
||||
}
|
||||
|
||||
export default AgentState;
|
||||
|
||||
@@ -16,11 +16,14 @@ from opendevin.core.schema import AgentState
|
||||
from opendevin.events import EventSource, EventStream, EventStreamSubscriber
|
||||
from opendevin.events.action import (
|
||||
Action,
|
||||
ActionConfirmationStatus,
|
||||
AddTaskAction,
|
||||
AgentDelegateAction,
|
||||
AgentFinishAction,
|
||||
AgentRejectAction,
|
||||
ChangeAgentStateAction,
|
||||
CmdRunAction,
|
||||
IPythonRunCellAction,
|
||||
MessageAction,
|
||||
ModifyTaskAction,
|
||||
NullAction,
|
||||
@@ -49,6 +52,7 @@ class AgentController:
|
||||
max_iterations: int
|
||||
event_stream: EventStream
|
||||
state: State
|
||||
confirmation_mode: bool
|
||||
agent_task: Optional[asyncio.Task] = None
|
||||
parent: 'AgentController | None' = None
|
||||
delegate: 'AgentController | None' = None
|
||||
@@ -60,6 +64,7 @@ class AgentController:
|
||||
event_stream: EventStream,
|
||||
sid: str = 'default',
|
||||
max_iterations: int | None = MAX_ITERATIONS,
|
||||
confirmation_mode: bool = False,
|
||||
max_budget_per_task: float | None = MAX_BUDGET_PER_TASK,
|
||||
initial_state: State | None = None,
|
||||
is_delegate: bool = False,
|
||||
@@ -92,6 +97,7 @@ class AgentController:
|
||||
self.set_initial_state(
|
||||
state=initial_state,
|
||||
max_iterations=max_iterations,
|
||||
confirmation_mode=confirmation_mode,
|
||||
)
|
||||
|
||||
self.max_budget_per_task = max_budget_per_task
|
||||
@@ -170,8 +176,19 @@ class AgentController:
|
||||
self.state.outputs = event.outputs # type: ignore[attr-defined]
|
||||
await self.set_agent_state_to(AgentState.REJECTED)
|
||||
elif isinstance(event, Observation):
|
||||
if (
|
||||
self._pending_action
|
||||
and hasattr(self._pending_action, 'is_confirmed')
|
||||
and self._pending_action.is_confirmed
|
||||
== ActionConfirmationStatus.AWAITING_CONFIRMATION
|
||||
):
|
||||
return
|
||||
if self._pending_action and self._pending_action.id == event.cause:
|
||||
self._pending_action = None
|
||||
if self.state.agent_state == AgentState.USER_CONFIRMED:
|
||||
await self.set_agent_state_to(AgentState.RUNNING)
|
||||
if self.state.agent_state == AgentState.USER_REJECTED:
|
||||
await self.set_agent_state_to(AgentState.AWAITING_USER_INPUT)
|
||||
logger.info(event, extra={'msg_type': 'OBSERVATION'})
|
||||
elif isinstance(event, CmdOutputObservation):
|
||||
logger.info(event, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -205,6 +222,18 @@ class AgentController:
|
||||
if new_state == AgentState.STOPPED or new_state == AgentState.ERROR:
|
||||
self.reset_task()
|
||||
|
||||
if self._pending_action is not None and (
|
||||
new_state == AgentState.USER_CONFIRMED
|
||||
or new_state == AgentState.USER_REJECTED
|
||||
):
|
||||
if hasattr(self._pending_action, 'thought'):
|
||||
self._pending_action.thought = '' # type: ignore[union-attr]
|
||||
if new_state == AgentState.USER_CONFIRMED:
|
||||
self._pending_action.is_confirmed = ActionConfirmationStatus.CONFIRMED # type: ignore[attr-defined]
|
||||
else:
|
||||
self._pending_action.is_confirmed = ActionConfirmationStatus.REJECTED # type: ignore[attr-defined]
|
||||
self.event_stream.add_event(self._pending_action, EventSource.AGENT)
|
||||
|
||||
self.event_stream.add_event(
|
||||
AgentStateChangedObservation('', self.state.agent_state), EventSource.AGENT
|
||||
)
|
||||
@@ -346,9 +375,19 @@ class AgentController:
|
||||
return
|
||||
|
||||
if action.runnable:
|
||||
if self.state.confirmation_mode and (
|
||||
type(action) is CmdRunAction or type(action) is IPythonRunCellAction
|
||||
):
|
||||
action.is_confirmed = ActionConfirmationStatus.AWAITING_CONFIRMATION
|
||||
self._pending_action = action
|
||||
|
||||
if not isinstance(action, NullAction):
|
||||
if (
|
||||
hasattr(action, 'is_confirmed')
|
||||
and action.is_confirmed
|
||||
== ActionConfirmationStatus.AWAITING_CONFIRMATION
|
||||
):
|
||||
await self.set_agent_state_to(AgentState.AWAITING_USER_CONFIRMATION)
|
||||
self.event_stream.add_event(action, EventSource.AGENT)
|
||||
|
||||
await self.update_state_after_step()
|
||||
@@ -362,12 +401,19 @@ class AgentController:
|
||||
return self.state
|
||||
|
||||
def set_initial_state(
|
||||
self, state: State | None, max_iterations: int = MAX_ITERATIONS
|
||||
self,
|
||||
state: State | None,
|
||||
max_iterations: int = MAX_ITERATIONS,
|
||||
confirmation_mode: bool = False,
|
||||
):
|
||||
# state from the previous session, state from a parent agent, or a new state
|
||||
# note that this is called twice when restoring a previous session, first with state=None
|
||||
if state is None:
|
||||
self.state = State(inputs={}, max_iterations=max_iterations)
|
||||
self.state = State(
|
||||
inputs={},
|
||||
max_iterations=max_iterations,
|
||||
confirmation_mode=confirmation_mode,
|
||||
)
|
||||
else:
|
||||
self.state = state
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ class State:
|
||||
root_task: RootTask = field(default_factory=RootTask)
|
||||
iteration: int = 0
|
||||
max_iterations: int = 100
|
||||
confirmation_mode: bool = False
|
||||
history: ShortTermHistory = field(default_factory=ShortTermHistory)
|
||||
inputs: dict = field(default_factory=dict)
|
||||
outputs: dict = field(default_factory=dict)
|
||||
|
||||
@@ -223,6 +223,7 @@ class AppConfig(metaclass=Singleton):
|
||||
workspace_mount_rewrite: str | None = None
|
||||
cache_dir: str = '/tmp/cache'
|
||||
run_as_devin: bool = True
|
||||
confirmation_mode: bool = False
|
||||
max_iterations: int = 100
|
||||
max_budget_per_task: float | None = None
|
||||
e2b_api_key: str = ''
|
||||
|
||||
@@ -37,3 +37,15 @@ class AgentState(str, Enum):
|
||||
ERROR = 'error'
|
||||
"""An error occurred during the task.
|
||||
"""
|
||||
|
||||
AWAITING_USER_CONFIRMATION = 'awaiting_user_confirmation'
|
||||
"""The agent is awaiting user confirmation.
|
||||
"""
|
||||
|
||||
USER_CONFIRMED = 'user_confirmed'
|
||||
"""The user confirmed the agent's action.
|
||||
"""
|
||||
|
||||
USER_REJECTED = 'user_rejected'
|
||||
"""The user rejected the agent's action.
|
||||
"""
|
||||
|
||||
@@ -20,6 +20,7 @@ class ConfigType(str, Enum):
|
||||
WORKSPACE_MOUNT_PATH_IN_SANDBOX = 'WORKSPACE_MOUNT_PATH_IN_SANDBOX'
|
||||
CACHE_DIR = 'CACHE_DIR'
|
||||
LLM_MODEL = 'LLM_MODEL'
|
||||
CONFIRMATION_MODE = 'CONFIRMATION_MODE'
|
||||
SANDBOX_CONTAINER_IMAGE = 'SANDBOX_CONTAINER_IMAGE'
|
||||
RUN_AS_DEVIN = 'RUN_AS_DEVIN'
|
||||
LLM_EMBEDDING_MODEL = 'LLM_EMBEDDING_MODEL'
|
||||
|
||||
@@ -44,5 +44,7 @@ class ObservationTypeSchema(BaseModel):
|
||||
|
||||
AGENT_STATE_CHANGED: str = Field(default='agent_state_changed')
|
||||
|
||||
USER_REJECTED: str = Field(default='user_rejected')
|
||||
|
||||
|
||||
ObservationType = ObservationTypeSchema()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from .action import Action
|
||||
from .action import Action, ActionConfirmationStatus
|
||||
from .agent import (
|
||||
AgentDelegateAction,
|
||||
AgentFinishAction,
|
||||
@@ -32,4 +32,5 @@ __all__ = [
|
||||
'ChangeAgentStateAction',
|
||||
'IPythonRunCellAction',
|
||||
'MessageAction',
|
||||
'ActionConfirmationStatus',
|
||||
]
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import ClassVar
|
||||
|
||||
from opendevin.events.event import Event
|
||||
|
||||
|
||||
class ActionConfirmationStatus(str, Enum):
|
||||
CONFIRMED = 'confirmed'
|
||||
REJECTED = 'rejected'
|
||||
AWAITING_CONFIRMATION = 'awaiting_confirmation'
|
||||
|
||||
|
||||
@dataclass
|
||||
class Action(Event):
|
||||
runnable: ClassVar[bool] = False
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import ClassVar
|
||||
|
||||
from opendevin.core.schema import ActionType
|
||||
|
||||
from .action import Action
|
||||
from .action import Action, ActionConfirmationStatus
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -12,6 +12,7 @@ class CmdRunAction(Action):
|
||||
thought: str = ''
|
||||
action: str = ActionType.RUN
|
||||
runnable: ClassVar[bool] = True
|
||||
is_confirmed: ActionConfirmationStatus = ActionConfirmationStatus.CONFIRMED
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
@@ -31,6 +32,7 @@ class IPythonRunCellAction(Action):
|
||||
thought: str = ''
|
||||
action: str = ActionType.RUN_IPYTHON
|
||||
runnable: ClassVar[bool] = True
|
||||
is_confirmed: ActionConfirmationStatus = ActionConfirmationStatus.CONFIRMED
|
||||
kernel_init_code: str = '' # code to run in the kernel (if the kernel is restarted)
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
@@ -7,6 +7,7 @@ from .error import ErrorObservation
|
||||
from .files import FileReadObservation, FileWriteObservation
|
||||
from .observation import Observation
|
||||
from .recall import AgentRecallObservation
|
||||
from .reject import RejectObservation
|
||||
from .success import SuccessObservation
|
||||
|
||||
__all__ = [
|
||||
@@ -22,4 +23,5 @@ __all__ = [
|
||||
'AgentStateChangedObservation',
|
||||
'AgentDelegateObservation',
|
||||
'SuccessObservation',
|
||||
'RejectObservation',
|
||||
]
|
||||
|
||||
18
opendevin/events/observation/reject.py
Normal file
18
opendevin/events/observation/reject.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from opendevin.core.schema import ObservationType
|
||||
|
||||
from .observation import Observation
|
||||
|
||||
|
||||
@dataclass
|
||||
class RejectObservation(Observation):
|
||||
"""
|
||||
This data class represents the result of a successful action.
|
||||
"""
|
||||
|
||||
observation: str = ObservationType.USER_REJECTED
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
return self.content
|
||||
@@ -10,6 +10,7 @@ from opendevin.events.observation.error import ErrorObservation
|
||||
from opendevin.events.observation.files import FileReadObservation, FileWriteObservation
|
||||
from opendevin.events.observation.observation import Observation
|
||||
from opendevin.events.observation.recall import AgentRecallObservation
|
||||
from opendevin.events.observation.reject import RejectObservation
|
||||
from opendevin.events.observation.success import SuccessObservation
|
||||
|
||||
observations = (
|
||||
@@ -24,6 +25,7 @@ observations = (
|
||||
SuccessObservation,
|
||||
ErrorObservation,
|
||||
AgentStateChangedObservation,
|
||||
RejectObservation,
|
||||
)
|
||||
|
||||
OBSERVATION_TYPE_TO_CLASS = {
|
||||
|
||||
@@ -7,6 +7,7 @@ from opendevin.core.logger import opendevin_logger as logger
|
||||
from opendevin.events import EventStream, EventStreamSubscriber
|
||||
from opendevin.events.action import (
|
||||
Action,
|
||||
ActionConfirmationStatus,
|
||||
AgentRecallAction,
|
||||
BrowseInteractiveAction,
|
||||
BrowseURLAction,
|
||||
@@ -20,6 +21,7 @@ from opendevin.events.observation import (
|
||||
ErrorObservation,
|
||||
NullObservation,
|
||||
Observation,
|
||||
RejectObservation,
|
||||
)
|
||||
from opendevin.events.serialization.action import ACTION_TYPE_TO_CLASS
|
||||
from opendevin.runtime import (
|
||||
@@ -115,6 +117,11 @@ class Runtime:
|
||||
"""
|
||||
if not action.runnable:
|
||||
return NullObservation('')
|
||||
if (
|
||||
hasattr(action, 'is_confirmed')
|
||||
and action.is_confirmed == ActionConfirmationStatus.AWAITING_CONFIRMATION
|
||||
):
|
||||
return NullObservation('')
|
||||
action_type = action.action # type: ignore[attr-defined]
|
||||
if action_type not in ACTION_TYPE_TO_CLASS:
|
||||
return ErrorObservation(f'Action {action_type} does not exist.')
|
||||
@@ -122,6 +129,13 @@ class Runtime:
|
||||
return ErrorObservation(
|
||||
f'Action {action_type} is not supported in the current runtime.'
|
||||
)
|
||||
if (
|
||||
hasattr(action, 'is_confirmed')
|
||||
and action.is_confirmed == ActionConfirmationStatus.REJECTED
|
||||
):
|
||||
return RejectObservation(
|
||||
'Action has been rejected by the user! Waiting for further user input.'
|
||||
)
|
||||
observation = await getattr(self, action_type)(action)
|
||||
observation._parent = action.id # type: ignore[attr-defined]
|
||||
return observation
|
||||
|
||||
@@ -217,7 +217,7 @@ async def websocket_endpoint(websocket: WebSocket):
|
||||
```
|
||||
- Run a command:
|
||||
```json
|
||||
{"action": "run", "args": {"command": "ls -l", "thought": ""}}
|
||||
{"action": "run", "args": {"command": "ls -l", "thought": "", "is_confirmed": "confirmed"}}
|
||||
```
|
||||
- Run an IPython command:
|
||||
```json
|
||||
|
||||
@@ -91,6 +91,9 @@ class AgentSession:
|
||||
model = args.get(ConfigType.LLM_MODEL, llm_config.model)
|
||||
api_key = args.get(ConfigType.LLM_API_KEY, llm_config.api_key)
|
||||
api_base = llm_config.base_url
|
||||
confirmation_mode = args.get(
|
||||
ConfigType.CONFIRMATION_MODE, config.confirmation_mode
|
||||
)
|
||||
max_iterations = args.get(ConfigType.MAX_ITERATIONS, config.max_iterations)
|
||||
|
||||
logger.info(f'Creating agent {agent_cls} using LLM {model}')
|
||||
@@ -110,6 +113,7 @@ class AgentSession:
|
||||
event_stream=self.event_stream,
|
||||
agent=agent,
|
||||
max_iterations=int(max_iterations),
|
||||
confirmation_mode=confirmation_mode,
|
||||
)
|
||||
try:
|
||||
agent_state = State.restore_from_session(self.sid)
|
||||
|
||||
@@ -41,7 +41,7 @@ as well as observations you've made. This only includes the MOST RECENT
|
||||
actions and observations--more may have happened before that.
|
||||
They are time-ordered, with your most recent action at the bottom.
|
||||
|
||||
[{"source": "agent", "action": "run", "args": {"command": "ls", "thought": ""}}, {"source": "agent", "observation": "run", "content": "bad.txt", "extras": {"command_id": -1, "command": "ls", "exit_code": 0}}]
|
||||
[{"source": "agent", "action": "run", "args": {"command": "ls", "thought": "", "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "bad.txt", "extras": {"command_id": -1, "command": "ls", "exit_code": 0}}]
|
||||
|
||||
## Format
|
||||
Your response MUST be in JSON format. It must be an object, and it must contain two fields:
|
||||
|
||||
@@ -41,7 +41,7 @@ as well as observations you've made. This only includes the MOST RECENT
|
||||
actions and observations--more may have happened before that.
|
||||
They are time-ordered, with your most recent action at the bottom.
|
||||
|
||||
[{"source": "agent", "action": "run", "args": {"command": "ls", "thought": ""}}, {"source": "agent", "observation": "run", "content": "bad.txt", "extras": {"command_id": -1, "command": "ls", "exit_code": 0}}, {"source": "agent", "action": "read", "args": {"path": "bad.txt", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "read", "content": "This is a stupid typoo.\nReally?\nNo mor typos!\nEnjoy!\n", "extras": {"path": "bad.txt"}}]
|
||||
[{"source": "agent", "action": "run", "args": {"command": "ls", "thought": "", "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "bad.txt", "extras": {"command_id": -1, "command": "ls", "exit_code": 0}}, {"source": "agent", "action": "read", "args": {"path": "bad.txt", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "read", "content": "This is a stupid typoo.\nReally?\nNo mor typos!\nEnjoy!\n", "extras": {"path": "bad.txt"}}]
|
||||
|
||||
## Format
|
||||
Your response MUST be in JSON format. It must be an object, and it must contain two fields:
|
||||
|
||||
@@ -41,7 +41,7 @@ as well as observations you've made. This only includes the MOST RECENT
|
||||
actions and observations--more may have happened before that.
|
||||
They are time-ordered, with your most recent action at the bottom.
|
||||
|
||||
[{"source": "agent", "action": "run", "args": {"command": "ls", "thought": ""}}, {"source": "agent", "observation": "run", "content": "", "extras": {"command_id": -1, "command": "ls", "exit_code": 0}}]
|
||||
[{"source": "agent", "action": "run", "args": {"command": "ls", "thought": "", "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "", "extras": {"command_id": -1, "command": "ls", "exit_code": 0}}]
|
||||
|
||||
## Format
|
||||
Your response MUST be in JSON format. It must be an object, and it must contain two fields:
|
||||
|
||||
@@ -41,7 +41,7 @@ as well as observations you've made. This only includes the MOST RECENT
|
||||
actions and observations--more may have happened before that.
|
||||
They are time-ordered, with your most recent action at the bottom.
|
||||
|
||||
[{"source": "agent", "action": "write", "args": {"path": "hello.sh", "content": "#!/bin/bash\n\n# Print hello\necho 'hello'", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "write", "content": "", "extras": {"path": "hello.sh"}}, {"source": "agent", "action": "run", "args": {"command": "chmod +x hello.sh", "thought": ""}}, {"source": "agent", "observation": "run", "content": "", "extras": {"command_id": -1, "command": "chmod +x hello.sh", "exit_code": 0}}]
|
||||
[{"source": "agent", "action": "write", "args": {"path": "hello.sh", "content": "#!/bin/bash\n\n# Print hello\necho 'hello'", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "write", "content": "", "extras": {"path": "hello.sh"}}, {"source": "agent", "action": "run", "args": {"command": "chmod +x hello.sh", "thought": "", "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "", "extras": {"command_id": -1, "command": "chmod +x hello.sh", "exit_code": 0}}]
|
||||
|
||||
## Format
|
||||
Your response MUST be in JSON format. It must be an object, and it must contain two fields:
|
||||
|
||||
@@ -41,7 +41,7 @@ as well as observations you've made. This only includes the MOST RECENT
|
||||
actions and observations--more may have happened before that.
|
||||
They are time-ordered, with your most recent action at the bottom.
|
||||
|
||||
[{"source": "agent", "action": "write", "args": {"path": "hello.sh", "content": "#!/bin/bash\n\n# Print hello\necho 'hello'", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "write", "content": "", "extras": {"path": "hello.sh"}}, {"source": "agent", "action": "run", "args": {"command": "chmod +x hello.sh", "thought": ""}}, {"source": "agent", "observation": "run", "content": "", "extras": {"command_id": -1, "command": "chmod +x hello.sh", "exit_code": 0}}, {"source": "agent", "action": "run", "args": {"command": "./hello.sh", "thought": ""}}, {"source": "agent", "observation": "run", "content": "hello", "extras": {"command_id": -1, "command": "./hello.sh", "exit_code": 0}}]
|
||||
[{"source": "agent", "action": "write", "args": {"path": "hello.sh", "content": "#!/bin/bash\n\n# Print hello\necho 'hello'", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "write", "content": "", "extras": {"path": "hello.sh"}}, {"source": "agent", "action": "run", "args": {"command": "chmod +x hello.sh", "thought": "", "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "", "extras": {"command_id": -1, "command": "chmod +x hello.sh", "exit_code": 0}}, {"source": "agent", "action": "run", "args": {"command": "./hello.sh", "thought": "", "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "hello", "extras": {"command_id": -1, "command": "./hello.sh", "exit_code": 0}}]
|
||||
|
||||
## Format
|
||||
Your response MUST be in JSON format. It must be an object, and it must contain two fields:
|
||||
|
||||
@@ -39,7 +39,7 @@ as well as observations you've made. This only includes the MOST RECENT
|
||||
actions and observations--more may have happened before that.
|
||||
They are time-ordered, with your most recent action at the bottom.
|
||||
|
||||
[{"source": "agent", "action": "read", "args": {"path": "hello.sh", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "read", "content": "#!/bin/bash\n\n# Print hello\necho 'hello'\n", "extras": {"path": "hello.sh"}}, {"source": "agent", "action": "run", "args": {"command": "bash hello.sh", "thought": ""}}, {"source": "agent", "observation": "run", "content": "hello", "extras": {"command_id": -1, "command": "bash hello.sh", "exit_code": 0}}]
|
||||
[{"source": "agent", "action": "read", "args": {"path": "hello.sh", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "read", "content": "#!/bin/bash\n\n# Print hello\necho 'hello'\n", "extras": {"path": "hello.sh"}}, {"source": "agent", "action": "run", "args": {"command": "bash hello.sh", "thought": "", "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "hello", "extras": {"command_id": -1, "command": "bash hello.sh", "exit_code": 0}}]
|
||||
|
||||
## Format
|
||||
Your response MUST be in JSON format. It must be an object, and it must contain two fields:
|
||||
|
||||
@@ -28,7 +28,7 @@ as well as observations you've made. This only includes the MOST RECENT
|
||||
actions and observations--more may have happened before that.
|
||||
They are time-ordered, with your most recent action at the bottom.
|
||||
|
||||
[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": ""}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}]
|
||||
[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": "", "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}]
|
||||
|
||||
If the last item in the history is an error, you should try to fix it.
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ as well as observations you've made. This only includes the MOST RECENT
|
||||
actions and observations--more may have happened before that.
|
||||
They are time-ordered, with your most recent action at the bottom.
|
||||
|
||||
[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": ""}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}]
|
||||
[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": "", "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}]
|
||||
|
||||
If the last item in the history is an error, you should try to fix it.
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ as well as observations you've made. This only includes the MOST RECENT
|
||||
actions and observations--more may have happened before that.
|
||||
They are time-ordered, with your most recent action at the bottom.
|
||||
|
||||
[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": ""}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}]
|
||||
[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": "", "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}]
|
||||
|
||||
If the last item in the history is an error, you should try to fix it.
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ as well as observations you've made. This only includes the MOST RECENT
|
||||
actions and observations--more may have happened before that.
|
||||
They are time-ordered, with your most recent action at the bottom.
|
||||
|
||||
[[{"source": "agent", "action": "write", "args": {"path": "hello.sh", "content": "#!/bin/bash\n\n# Print 'hello'\necho 'hello'", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "write", "content": "", "extras": {"path": "hello.sh"}}], [{"source": "agent", "action": "run", "args": {"command": "chmod +x hello.sh", "background": false, "thought": ""}}, {"source": "agent", "observation": "run", "content": "", "extras": {"command_id": -1, "command": "chmod +x hello.sh", "exit_code": 0}}]]
|
||||
[[{"source": "agent", "action": "write", "args": {"path": "hello.sh", "content": "#!/bin/bash\n\n# Print 'hello'\necho 'hello'", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "write", "content": "", "extras": {"path": "hello.sh"}}], [{"source": "agent", "action": "run", "args": {"command": "chmod +x hello.sh", "background": false, "thought": "", "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "", "extras": {"command_id": -1, "command": "chmod +x hello.sh", "exit_code": 0}}]]
|
||||
|
||||
## Format
|
||||
Your response MUST be in JSON format. It must be an object, and it must contain two fields:
|
||||
|
||||
@@ -38,7 +38,7 @@ as well as observations you've made. This only includes the MOST RECENT
|
||||
actions and observations--more may have happened before that.
|
||||
They are time-ordered, with your most recent action at the bottom.
|
||||
|
||||
[[{"source": "agent", "action": "write", "args": {"path": "hello.sh", "content": "#!/bin/bash\n\n# Print 'hello'\necho 'hello'", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "write", "content": "", "extras": {"path": "hello.sh"}}], [{"source": "agent", "action": "run", "args": {"command": "chmod +x hello.sh", "background": false, "thought": ""}}, {"source": "agent", "observation": "run", "content": "", "extras": {"command_id": -1, "command": "chmod +x hello.sh", "exit_code": 0}}], [{"source": "agent", "action": "run", "args": {"command": "./hello.sh", "background": false, "thought": ""}}, {"source": "agent", "observation": "run", "content": "hello", "extras": {"command_id": -1, "command": "./hello.sh", "exit_code": 0}}]]
|
||||
[[{"source": "agent", "action": "write", "args": {"path": "hello.sh", "content": "#!/bin/bash\n\n# Print 'hello'\necho 'hello'", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "write", "content": "", "extras": {"path": "hello.sh"}}], [{"source": "agent", "action": "run", "args": {"command": "chmod +x hello.sh", "background": false, "thought": "", "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "", "extras": {"command_id": -1, "command": "chmod +x hello.sh", "exit_code": 0}}], [{"source": "agent", "action": "run", "args": {"command": "./hello.sh", "background": false, "thought": "", "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "hello", "extras": {"command_id": -1, "command": "./hello.sh", "exit_code": 0}}]]
|
||||
|
||||
## Format
|
||||
Your response MUST be in JSON format. It must be an object, and it must contain two fields:
|
||||
|
||||
@@ -60,7 +60,7 @@ as well as observations you've made. This only includes the MOST RECENT
|
||||
actions and observations--more may have happened before that.
|
||||
They are time-ordered, with your most recent action at the bottom.
|
||||
|
||||
[[{"source": "user", "action": "message", "args": {"content": "Write a shell script 'hello.sh' that prints 'hello'. Do not ask me for confirmation at any point.", "wait_for_response": false}}, {"observation": "null", "content": "", "extras": {}}], [{"source": "agent", "action": "delegate", "args": {"agent": "CoderAgent", "inputs": {"task": "Write a shell script 'hello.sh' that prints 'hello'.", "summary": ""}, "thought": ""}}, {"observation": "null", "content": "", "extras": {}}], [{"action": "null", "args": {}}, {"source": "agent", "observation": "delegate", "content": "", "extras": {"outputs": {}}}]]
|
||||
[[{"source": "user", "action": "message", "args": {"content": "Write a shell script 'hello.sh' that prints 'hello'. Do not ask me for confirmation at any point.", "wait_for_response": false}}, {"observation": "null", "content": "", "extras": {}}], [{"source": "agent", "action": "delegate", "args": {"agent": "CoderAgent", "inputs": {"task": "Write a shell script 'hello.sh' that prints 'hello'.", "summary": ""}, "thought": "", "is_confirmed": "confirmed"}}, {"observation": "null", "content": "", "extras": {}}], [{"action": "null", "args": {}}, {"source": "agent", "observation": "delegate", "content": "", "extras": {"outputs": {}}}]]
|
||||
|
||||
## Available Actions
|
||||
* `delegate` - send a task to another agent from the list provided. Arguments:
|
||||
|
||||
@@ -40,7 +40,7 @@ as well as observations you've made. This only includes the MOST RECENT
|
||||
actions and observations--more may have happened before that.
|
||||
They are time-ordered, with your most recent action at the bottom.
|
||||
|
||||
[[{"source": "agent", "action": "read", "args": {"path": "hello.sh", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "read", "content": "#!/bin/bash\n\n# Print 'hello'\necho 'hello'\n", "extras": {"path": "hello.sh"}}], [{"source": "agent", "action": "run", "args": {"command": "bash hello.sh", "background": false, "thought": ""}}, {"source": "agent", "observation": "run", "content": "hello", "extras": {"command_id": -1, "command": "bash hello.sh", "exit_code": 0}}]]
|
||||
[[{"source": "agent", "action": "read", "args": {"path": "hello.sh", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "read", "content": "#!/bin/bash\n\n# Print 'hello'\necho 'hello'\n", "extras": {"path": "hello.sh"}}], [{"source": "agent", "action": "run", "args": {"command": "bash hello.sh", "background": false, "thought": "", "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "hello", "extras": {"command_id": -1, "command": "bash hello.sh", "exit_code": 0}}]]
|
||||
|
||||
## Format
|
||||
Your response MUST be in JSON format. It must be an object, and it must contain two fields:
|
||||
|
||||
@@ -60,7 +60,7 @@ as well as observations you've made. This only includes the MOST RECENT
|
||||
actions and observations--more may have happened before that.
|
||||
They are time-ordered, with your most recent action at the bottom.
|
||||
|
||||
[[{"source": "user", "action": "message", "args": {"content": "Write a shell script 'hello.sh' that prints 'hello'. Do not ask me for confirmation at any point.", "wait_for_response": false}}, {"observation": "null", "content": "", "extras": {}}], [{"source": "agent", "action": "delegate", "args": {"agent": "CoderAgent", "inputs": {"task": "Write a shell script 'hello.sh' that prints 'hello'.", "summary": ""}, "thought": ""}}, {"observation": "null", "content": "", "extras": {}}], [{"action": "null", "args": {}}, {"source": "agent", "observation": "delegate", "content": "", "extras": {"outputs": {}}}], [{"source": "agent", "action": "delegate", "args": {"agent": "VerifierAgent", "inputs": {"task": "Verify that the shell script 'hello.sh' prints 'hello'."}, "thought": ""}}, {"observation": "null", "content": "", "extras": {}}], [{"action": "null", "args": {}}, {"source": "agent", "observation": "delegate", "content": "", "extras": {"outputs": {"completed": true}}}]]
|
||||
[[{"source": "user", "action": "message", "args": {"content": "Write a shell script 'hello.sh' that prints 'hello'. Do not ask me for confirmation at any point.", "wait_for_response": false}}, {"observation": "null", "content": "", "extras": {}}], [{"source": "agent", "action": "delegate", "args": {"agent": "CoderAgent", "inputs": {"task": "Write a shell script 'hello.sh' that prints 'hello'.", "summary": ""}, "thought": "", "is_confirmed": "confirmed"}}, {"observation": "null", "content": "", "extras": {}}], [{"action": "null", "args": {}}, {"source": "agent", "observation": "delegate", "content": "", "extras": {"outputs": {}}}], [{"source": "agent", "action": "delegate", "args": {"agent": "VerifierAgent", "inputs": {"task": "Verify that the shell script 'hello.sh' prints 'hello'."}, "thought": "", "is_confirmed": "confirmed"}}, {"observation": "null", "content": "", "extras": {}}], [{"action": "null", "args": {}}, {"source": "agent", "observation": "delegate", "content": "", "extras": {"outputs": {"completed": true}}}]]
|
||||
|
||||
## Available Actions
|
||||
* `delegate` - send a task to another agent from the list provided. Arguments:
|
||||
|
||||
@@ -135,7 +135,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "echo \"hello world\"",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -182,7 +183,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "node test.js",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -135,7 +135,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "echo \"hello world\"",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -182,7 +183,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "node test.js",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -330,7 +332,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "pwd",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -135,7 +135,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "echo \"hello world\"",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -182,7 +183,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "node test.js",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -330,7 +332,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "pwd",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -348,7 +351,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "ls",
|
||||
"thought": "I need to see the contents of the current directory to ensure there are no conflicts and to understand the environment better."
|
||||
"thought": "I need to see the contents of the current directory to ensure there are no conflicts and to understand the environment better.",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -135,7 +135,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "echo \"hello world\"",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -182,7 +183,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "node test.js",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -330,7 +332,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "pwd",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -348,7 +351,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "ls",
|
||||
"thought": "I need to see the contents of the current directory to ensure there are no conflicts and to understand the environment better."
|
||||
"thought": "I need to see the contents of the current directory to ensure there are no conflicts and to understand the environment better.",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -135,7 +135,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "echo \"hello world\"",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -182,7 +183,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "node test.js",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -330,7 +332,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "pwd",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -348,7 +351,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "ls",
|
||||
"thought": "I need to see the contents of the current directory to ensure there are no conflicts and to understand the environment better."
|
||||
"thought": "I need to see the contents of the current directory to ensure there are no conflicts and to understand the environment better.",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -135,7 +135,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "echo \"hello world\"",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -182,7 +183,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "node test.js",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -330,7 +332,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "pwd",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -348,7 +351,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "ls",
|
||||
"thought": "I need to see the contents of the current directory to ensure there are no conflicts and to understand the environment better."
|
||||
"thought": "I need to see the contents of the current directory to ensure there are no conflicts and to understand the environment better.",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -135,7 +135,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "echo \"hello world\"",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -182,7 +183,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "node test.js",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -330,7 +332,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "pwd",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -348,7 +351,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "ls",
|
||||
"thought": "I need to see the contents of the current directory to ensure there are no conflicts and to understand the environment better."
|
||||
"thought": "I need to see the contents of the current directory to ensure there are no conflicts and to understand the environment better.",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -401,7 +405,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "chmod +x hello.sh",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -135,7 +135,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "echo \"hello world\"",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -182,7 +183,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "node test.js",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -330,7 +332,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "pwd",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -348,7 +351,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "ls",
|
||||
"thought": "I need to see the contents of the current directory to ensure there are no conflicts and to understand the environment better."
|
||||
"thought": "I need to see the contents of the current directory to ensure there are no conflicts and to understand the environment better.",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -401,7 +405,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "chmod +x hello.sh",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -419,7 +424,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "./hello.sh",
|
||||
"thought": "I need to run the 'hello.sh' script to verify that it prints 'hello'."
|
||||
"thought": "I need to run the 'hello.sh' script to verify that it prints 'hello'.",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "ls",
|
||||
"thought": "I need to see the contents of the current directory to ensure there are no conflicts and to understand the environment better."
|
||||
"thought": "I need to see the contents of the current directory to ensure there are no conflicts and to understand the environment better.",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -196,7 +196,8 @@ ten actions--more happened before that.
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "bash hello.sh",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -195,7 +195,8 @@ ten actions--more happened before that.
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "bash hello.sh",
|
||||
"thought": ""
|
||||
"thought": "",
|
||||
"is_confirmed": "confirmed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ from opendevin.events.action import (
|
||||
MessageAction,
|
||||
ModifyTaskAction,
|
||||
)
|
||||
from opendevin.events.action.action import ActionConfirmationStatus
|
||||
from opendevin.events.serialization import (
|
||||
event_from_dict,
|
||||
event_to_dict,
|
||||
@@ -90,7 +91,11 @@ def test_agent_reject_action_serialization_deserialization():
|
||||
def test_cmd_run_action_serialization_deserialization():
|
||||
original_action_dict = {
|
||||
'action': 'run',
|
||||
'args': {'command': 'echo "Hello world"', 'thought': ''},
|
||||
'args': {
|
||||
'command': 'echo "Hello world"',
|
||||
'thought': '',
|
||||
'is_confirmed': ActionConfirmationStatus.CONFIRMED,
|
||||
},
|
||||
}
|
||||
serialization_deserialization(original_action_dict, CmdRunAction)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user