feat(webui): add project workspaces and access controls (#4007)
* feat(webui): add project workspaces and access controls * feat(webui): add project workspaces and access controls * refactor(tools): centralize workspace access resolution * refactor(webui): remove unused workspace host state * fix(webui): hide estimated file edit label * fix(webui): clarify file edit deletion feedback * fix(webui): label deleted file activity * fix(webui): flatten file edit activity rows * fix(core): remove path-only patch deletion * fix(core): keep apply patch non-destructive * refactor(webui): trim workspace host plumbing * fix(tools): register exec with tools config
This commit is contained in:
@@ -433,6 +433,72 @@ describe("AgentActivityCluster", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("labels whole-file deletes as deleted instead of edited", () => {
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={activityMessages("", {
|
||||
id: "t-delete",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: "apply_patch()",
|
||||
traces: ["apply_patch()"],
|
||||
fileEdits: [{
|
||||
call_id: "call-delete",
|
||||
tool: "apply_patch",
|
||||
path: "angry-birds.html",
|
||||
phase: "end",
|
||||
added: 0,
|
||||
deleted: 590,
|
||||
approximate: false,
|
||||
status: "done",
|
||||
operation: "delete",
|
||||
}],
|
||||
createdAt: 3,
|
||||
})}
|
||||
isTurnStreaming={false}
|
||||
hasBodyBelow={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("button", { name: /deleted angry-birds\.html/i })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: /edited angry-birds\.html/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders file-only edits without a redundant disclosure", () => {
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={[{
|
||||
id: "t-file-only",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: "apply_patch()",
|
||||
traces: ["apply_patch()"],
|
||||
fileEdits: [{
|
||||
call_id: "call-patch",
|
||||
tool: "apply_patch",
|
||||
path: "src/app.tsx",
|
||||
absolute_path: "/Users/renxubin/project/src/app.tsx",
|
||||
phase: "end",
|
||||
added: 12,
|
||||
deleted: 3,
|
||||
approximate: false,
|
||||
status: "done",
|
||||
}],
|
||||
createdAt: 3,
|
||||
}]}
|
||||
isTurnStreaming={false}
|
||||
hasBodyBelow={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByRole("button", { name: /edited app\.tsx/i })).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("agent-activity-scroll")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Edited")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("activity-header-file-reference")).toHaveTextContent("app.tsx");
|
||||
expect(screen.getByText("+12")).toBeInTheDocument();
|
||||
expect(screen.getByText("-3")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders CLI app runs as dedicated activity rows", () => {
|
||||
const line = 'run_cli_app({"name":"blender","args":["--background","scene.blend"],"json":true})';
|
||||
render(
|
||||
@@ -771,6 +837,38 @@ describe("AgentActivityCluster", () => {
|
||||
expect(screen.getByText("Preparing file edit…")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows the reason when a file edit fails", () => {
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={activityMessages("", {
|
||||
id: "t2",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: "apply_patch()",
|
||||
traces: ["apply_patch()"],
|
||||
fileEdits: [{
|
||||
call_id: "call-patch",
|
||||
tool: "apply_patch",
|
||||
path: "angry-birds.html",
|
||||
phase: "error",
|
||||
added: 0,
|
||||
deleted: 0,
|
||||
approximate: false,
|
||||
status: "error",
|
||||
error: "Error applying patch: old_text not found in angry-birds.html",
|
||||
}],
|
||||
createdAt: 3,
|
||||
})}
|
||||
isTurnStreaming={false}
|
||||
hasBodyBelow={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /failed angry-birds\.html/i }));
|
||||
|
||||
expect(screen.getByText("Target text was not found in angry-birds.html.")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("merges repeated edits for the same path and lets successful edits win over failures", async () => {
|
||||
const restoreMotion = installReducedMotion();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user