|
@@ -146,6 +146,156 @@ describe("ChatView - Auto Approval Tests", () => {
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ it("auto-approves outside workspace read operations when enabled", async () => {
|
|
|
|
|
+ render(
|
|
|
|
|
+ <ExtensionStateContextProvider>
|
|
|
|
|
+ <ChatView
|
|
|
|
|
+ isHidden={false}
|
|
|
|
|
+ showAnnouncement={false}
|
|
|
|
|
+ hideAnnouncement={() => {}}
|
|
|
|
|
+ showHistoryView={() => {}}
|
|
|
|
|
+ />
|
|
|
|
|
+ </ExtensionStateContextProvider>,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // First hydrate state with initial task
|
|
|
|
|
+ mockPostMessage({
|
|
|
|
|
+ alwaysAllowReadOnly: true,
|
|
|
|
|
+ alwaysAllowReadOnlyOutsideWorkspace: true,
|
|
|
|
|
+ autoApprovalEnabled: true,
|
|
|
|
|
+ clineMessages: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "say",
|
|
|
|
|
+ say: "task",
|
|
|
|
|
+ ts: Date.now() - 2000,
|
|
|
|
|
+ text: "Initial task",
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Then send the read tool ask message with an absolute path (outside workspace)
|
|
|
|
|
+ mockPostMessage({
|
|
|
|
|
+ alwaysAllowReadOnly: true,
|
|
|
|
|
+ alwaysAllowReadOnlyOutsideWorkspace: true,
|
|
|
|
|
+ autoApprovalEnabled: true,
|
|
|
|
|
+ clineMessages: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "say",
|
|
|
|
|
+ say: "task",
|
|
|
|
|
+ ts: Date.now() - 2000,
|
|
|
|
|
+ text: "Initial task",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "ask",
|
|
|
|
|
+ ask: "tool",
|
|
|
|
|
+ ts: Date.now(),
|
|
|
|
|
+ text: JSON.stringify({
|
|
|
|
|
+ tool: "readFile",
|
|
|
|
|
+ path: "/absolute/path/test.txt",
|
|
|
|
|
+ // Use an absolute path that's clearly outside workspace
|
|
|
|
|
+ }),
|
|
|
|
|
+ partial: false,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Also mock the filePaths for workspace detection
|
|
|
|
|
+ mockPostMessage({
|
|
|
|
|
+ alwaysAllowReadOnly: true,
|
|
|
|
|
+ alwaysAllowReadOnlyOutsideWorkspace: true,
|
|
|
|
|
+ autoApprovalEnabled: true,
|
|
|
|
|
+ filePaths: ["/workspace/root", "/another/workspace"],
|
|
|
|
|
+ clineMessages: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "say",
|
|
|
|
|
+ say: "task",
|
|
|
|
|
+ ts: Date.now() - 2000,
|
|
|
|
|
+ text: "Initial task",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "ask",
|
|
|
|
|
+ ask: "tool",
|
|
|
|
|
+ ts: Date.now(),
|
|
|
|
|
+ text: JSON.stringify({
|
|
|
|
|
+ tool: "readFile",
|
|
|
|
|
+ path: "/absolute/path/test.txt",
|
|
|
|
|
+ }),
|
|
|
|
|
+ partial: false,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Wait for the auto-approval message
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(vscode.postMessage).toHaveBeenCalledWith({
|
|
|
|
|
+ type: "askResponse",
|
|
|
|
|
+ askResponse: "yesButtonClicked",
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("does not auto-approve outside workspace read operations without permission", async () => {
|
|
|
|
|
+ render(
|
|
|
|
|
+ <ExtensionStateContextProvider>
|
|
|
|
|
+ <ChatView
|
|
|
|
|
+ isHidden={false}
|
|
|
|
|
+ showAnnouncement={false}
|
|
|
|
|
+ hideAnnouncement={() => {}}
|
|
|
|
|
+ showHistoryView={() => {}}
|
|
|
|
|
+ />
|
|
|
|
|
+ </ExtensionStateContextProvider>,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // First hydrate state with initial task
|
|
|
|
|
+ mockPostMessage({
|
|
|
|
|
+ alwaysAllowReadOnly: true,
|
|
|
|
|
+ alwaysAllowReadOnlyOutsideWorkspace: false, // No permission for outside workspace
|
|
|
|
|
+ autoApprovalEnabled: true,
|
|
|
|
|
+ filePaths: ["/workspace/root", "/another/workspace"], // Same workspace paths as before
|
|
|
|
|
+ clineMessages: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "say",
|
|
|
|
|
+ say: "task",
|
|
|
|
|
+ ts: Date.now() - 2000,
|
|
|
|
|
+ text: "Initial task",
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Then send the read tool ask message with an absolute path (outside workspace)
|
|
|
|
|
+ mockPostMessage({
|
|
|
|
|
+ alwaysAllowReadOnly: true,
|
|
|
|
|
+ alwaysAllowReadOnlyOutsideWorkspace: false,
|
|
|
|
|
+ autoApprovalEnabled: true,
|
|
|
|
|
+ clineMessages: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "say",
|
|
|
|
|
+ say: "task",
|
|
|
|
|
+ ts: Date.now() - 2000,
|
|
|
|
|
+ text: "Initial task",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "ask",
|
|
|
|
|
+ ask: "tool",
|
|
|
|
|
+ ts: Date.now(),
|
|
|
|
|
+ text: JSON.stringify({
|
|
|
|
|
+ tool: "readFile",
|
|
|
|
|
+ path: "/absolute/path/test.txt",
|
|
|
|
|
+ isOutsideWorkspace: true, // Explicitly indicate this is outside workspace
|
|
|
|
|
+ }),
|
|
|
|
|
+ partial: false,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Wait a short time and verify no auto-approval message was sent
|
|
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, 100))
|
|
|
|
|
+ expect(vscode.postMessage).not.toHaveBeenCalledWith({
|
|
|
|
|
+ type: "askResponse",
|
|
|
|
|
+ askResponse: "yesButtonClicked",
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
it("does not auto-approve when autoApprovalEnabled is false", async () => {
|
|
it("does not auto-approve when autoApprovalEnabled is false", async () => {
|
|
|
render(
|
|
render(
|
|
|
<ExtensionStateContextProvider>
|
|
<ExtensionStateContextProvider>
|
|
@@ -258,6 +408,136 @@ describe("ChatView - Auto Approval Tests", () => {
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ it("auto-approves outside workspace write operations when enabled", async () => {
|
|
|
|
|
+ render(
|
|
|
|
|
+ <ExtensionStateContextProvider>
|
|
|
|
|
+ <ChatView
|
|
|
|
|
+ isHidden={false}
|
|
|
|
|
+ showAnnouncement={false}
|
|
|
|
|
+ hideAnnouncement={() => {}}
|
|
|
|
|
+ showHistoryView={() => {}}
|
|
|
|
|
+ />
|
|
|
|
|
+ </ExtensionStateContextProvider>,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // First hydrate state with initial task
|
|
|
|
|
+ mockPostMessage({
|
|
|
|
|
+ alwaysAllowWrite: true,
|
|
|
|
|
+ alwaysAllowWriteOutsideWorkspace: true,
|
|
|
|
|
+ autoApprovalEnabled: true,
|
|
|
|
|
+ writeDelayMs: 0, // Set to 0 for testing
|
|
|
|
|
+ filePaths: ["/workspace/root", "/another/workspace"], // Define workspace paths for testing
|
|
|
|
|
+ clineMessages: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "say",
|
|
|
|
|
+ say: "task",
|
|
|
|
|
+ ts: Date.now() - 2000,
|
|
|
|
|
+ text: "Initial task",
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Then send the write tool ask message with an absolute path (outside workspace)
|
|
|
|
|
+ mockPostMessage({
|
|
|
|
|
+ alwaysAllowWrite: true,
|
|
|
|
|
+ alwaysAllowWriteOutsideWorkspace: true,
|
|
|
|
|
+ autoApprovalEnabled: true,
|
|
|
|
|
+ writeDelayMs: 0,
|
|
|
|
|
+ clineMessages: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "say",
|
|
|
|
|
+ say: "task",
|
|
|
|
|
+ ts: Date.now() - 2000,
|
|
|
|
|
+ text: "Initial task",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "ask",
|
|
|
|
|
+ ask: "tool",
|
|
|
|
|
+ ts: Date.now(),
|
|
|
|
|
+ text: JSON.stringify({
|
|
|
|
|
+ tool: "editedExistingFile",
|
|
|
|
|
+ path: "/absolute/path/test.txt",
|
|
|
|
|
+ content: "Test content",
|
|
|
|
|
+ }),
|
|
|
|
|
+ partial: false,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Wait for the auto-approval message
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(vscode.postMessage).toHaveBeenCalledWith({
|
|
|
|
|
+ type: "askResponse",
|
|
|
|
|
+ askResponse: "yesButtonClicked",
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("does not auto-approve outside workspace write operations without permission", async () => {
|
|
|
|
|
+ render(
|
|
|
|
|
+ <ExtensionStateContextProvider>
|
|
|
|
|
+ <ChatView
|
|
|
|
|
+ isHidden={false}
|
|
|
|
|
+ showAnnouncement={false}
|
|
|
|
|
+ hideAnnouncement={() => {}}
|
|
|
|
|
+ showHistoryView={() => {}}
|
|
|
|
|
+ />
|
|
|
|
|
+ </ExtensionStateContextProvider>,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // First hydrate state with initial task
|
|
|
|
|
+ mockPostMessage({
|
|
|
|
|
+ alwaysAllowWrite: true,
|
|
|
|
|
+ alwaysAllowWriteOutsideWorkspace: false, // No permission for outside workspace
|
|
|
|
|
+ autoApprovalEnabled: true,
|
|
|
|
|
+ writeDelayMs: 0,
|
|
|
|
|
+ filePaths: ["/workspace/root", "/another/workspace"], // Define workspace paths for testing
|
|
|
|
|
+ clineMessages: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "say",
|
|
|
|
|
+ say: "task",
|
|
|
|
|
+ ts: Date.now() - 2000,
|
|
|
|
|
+ text: "Initial task",
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Then send the write tool ask message with an absolute path (outside workspace)
|
|
|
|
|
+ mockPostMessage({
|
|
|
|
|
+ alwaysAllowWrite: true,
|
|
|
|
|
+ alwaysAllowWriteOutsideWorkspace: false,
|
|
|
|
|
+ autoApprovalEnabled: true,
|
|
|
|
|
+ writeDelayMs: 0,
|
|
|
|
|
+ clineMessages: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "say",
|
|
|
|
|
+ say: "task",
|
|
|
|
|
+ ts: Date.now() - 2000,
|
|
|
|
|
+ text: "Initial task",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "ask",
|
|
|
|
|
+ ask: "tool",
|
|
|
|
|
+ ts: Date.now(),
|
|
|
|
|
+ text: JSON.stringify({
|
|
|
|
|
+ tool: "editedExistingFile",
|
|
|
|
|
+ path: "/absolute/path/test.txt",
|
|
|
|
|
+ content: "Test content",
|
|
|
|
|
+ isOutsideWorkspace: true, // Explicitly indicate this is outside workspace
|
|
|
|
|
+ }),
|
|
|
|
|
+ partial: false,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Wait a short time and verify no auto-approval message was sent
|
|
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, 100))
|
|
|
|
|
+ expect(vscode.postMessage).not.toHaveBeenCalledWith({
|
|
|
|
|
+ type: "askResponse",
|
|
|
|
|
+ askResponse: "yesButtonClicked",
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
it("auto-approves browser actions when enabled", async () => {
|
|
it("auto-approves browser actions when enabled", async () => {
|
|
|
render(
|
|
render(
|
|
|
<ExtensionStateContextProvider>
|
|
<ExtensionStateContextProvider>
|