mirror of
https://github.com/samsonjs/Peekaboo.git
synced 2026-04-27 15:07:41 +00:00
fix: Prevent format 'data' for screen captures to avoid stack overflow
- Screen captures now reject format: 'data' with clear error message - Large screen images cause JavaScript stack overflow when base64 encoded - Application window captures can still use format: 'data' - Update tests and documentation to reflect this limitation
This commit is contained in:
parent
338b994ac9
commit
30277bbf6c
5 changed files with 47 additions and 10 deletions
|
|
@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Restriction on using `format: "data"` for screen captures to prevent JavaScript stack overflow errors
|
||||||
|
- Screen captures must use `format: "png"` or omit the format parameter
|
||||||
|
- Application window captures can still use `format: "data"`
|
||||||
|
|
||||||
## [1.0.0-beta.18] - 2025-06-08
|
## [1.0.0-beta.18] - 2025-06-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -289,15 +289,17 @@ Peekaboo provides three main tools for AI agents:
|
||||||
|
|
||||||
Captures macOS screen content with automatic shadow/frame removal.
|
Captures macOS screen content with automatic shadow/frame removal.
|
||||||
|
|
||||||
|
**Important:** Screen captures cannot use `format: "data"` due to the large size of screen images causing JavaScript stack overflow errors. Always use `format: "png"` (or omit format) with a `path` for screen captures.
|
||||||
|
|
||||||
**Examples:**
|
**Examples:**
|
||||||
```javascript
|
```javascript
|
||||||
// Capture entire screen
|
// Capture entire screen (must save to file)
|
||||||
await use_mcp_tool("peekaboo", "image", {
|
await use_mcp_tool("peekaboo", "image", {
|
||||||
app_target: "screen:0",
|
app_target: "screen:0",
|
||||||
path: "~/Desktop/screenshot.png"
|
path: "~/Desktop/screenshot.png"
|
||||||
});
|
});
|
||||||
|
|
||||||
// Capture specific app window with analysis
|
// Capture specific app window with analysis (can use format: "data")
|
||||||
await use_mcp_tool("peekaboo", "image", {
|
await use_mcp_tool("peekaboo", "image", {
|
||||||
app_target: "Safari",
|
app_target: "Safari",
|
||||||
question: "What website is currently open?",
|
question: "What website is currently open?",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// This file is auto-generated by the build script. Do not edit manually.
|
// This file is auto-generated by the build script. Do not edit manually.
|
||||||
enum Version {
|
enum Version {
|
||||||
static let current = "1.0.0-beta.17"
|
static let current = "1.0.0-beta.18"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,23 @@ export async function imageToolHandler(
|
||||||
try {
|
try {
|
||||||
logger.debug({ input }, "Processing peekaboo.image tool call");
|
logger.debug({ input }, "Processing peekaboo.image tool call");
|
||||||
|
|
||||||
|
// Validate format restrictions for screen captures
|
||||||
|
const isScreenCapture = !input.app_target || input.app_target.startsWith("screen:");
|
||||||
|
if (isScreenCapture && input.format === "data") {
|
||||||
|
logger.warn("Screen capture with format 'data' is not allowed due to size constraints");
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: "Screen captures cannot use format 'data' because they produce images too large for base64 encoding. " +
|
||||||
|
"Please use format 'png' to save to a file instead.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isError: true,
|
||||||
|
_meta: { backend_error_code: "FORMAT_NOT_ALLOWED_FOR_SCREEN" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Determine effective path and format for Swift CLI
|
// Determine effective path and format for Swift CLI
|
||||||
const swiftFormat = input.format === "data" ? "png" : (input.format || "png");
|
const swiftFormat = input.format === "data" ? "png" : (input.format || "png");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,14 +109,26 @@ describe("Image Tool", () => {
|
||||||
expect(mockFsRm).not.toHaveBeenCalled();
|
expect(mockFsRm).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should capture screen with format: 'data'", async () => {
|
it("should reject screen capture with format: 'data'", async () => {
|
||||||
|
const result = await imageToolHandler(
|
||||||
|
{ format: "data" },
|
||||||
|
mockContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.isError).toBe(true);
|
||||||
|
expect(result.content[0].text).toContain("Screen captures cannot use format 'data'");
|
||||||
|
expect(result._meta?.backend_error_code).toBe("FORMAT_NOT_ALLOWED_FOR_SCREEN");
|
||||||
|
expect(mockExecuteSwiftCli).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow app capture with format: 'data'", async () => {
|
||||||
// Mock resolveImagePath to return a temp directory for format: "data"
|
// Mock resolveImagePath to return a temp directory for format: "data"
|
||||||
mockResolveImagePath.mockResolvedValue({
|
mockResolveImagePath.mockResolvedValue({
|
||||||
effectivePath: MOCK_TEMP_IMAGE_DIR,
|
effectivePath: MOCK_TEMP_IMAGE_DIR,
|
||||||
tempDirUsed: MOCK_TEMP_IMAGE_DIR,
|
tempDirUsed: MOCK_TEMP_IMAGE_DIR,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockResponse = mockSwiftCli.captureImage("screen", {
|
const mockResponse = mockSwiftCli.captureImage("Safari", {
|
||||||
path: MOCK_SAVED_FILE_PATH,
|
path: MOCK_SAVED_FILE_PATH,
|
||||||
format: "png",
|
format: "png",
|
||||||
});
|
});
|
||||||
|
|
@ -124,7 +136,7 @@ describe("Image Tool", () => {
|
||||||
mockReadImageAsBase64.mockResolvedValue("base64imagedata");
|
mockReadImageAsBase64.mockResolvedValue("base64imagedata");
|
||||||
|
|
||||||
const result = await imageToolHandler(
|
const result = await imageToolHandler(
|
||||||
{ format: "data" },
|
{ app_target: "Safari", format: "data" },
|
||||||
mockContext,
|
mockContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -140,7 +152,7 @@ describe("Image Tool", () => {
|
||||||
expect(mockFsRm).not.toHaveBeenCalled();
|
expect(mockFsRm).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should save file and return base64 when format: 'data' with path", async () => {
|
it("should save file and return base64 when format: 'data' with path for app capture", async () => {
|
||||||
const userPath = "/user/test.png";
|
const userPath = "/user/test.png";
|
||||||
// Mock resolveImagePath to return the user path (no temp dir)
|
// Mock resolveImagePath to return the user path (no temp dir)
|
||||||
mockResolveImagePath.mockResolvedValue({
|
mockResolveImagePath.mockResolvedValue({
|
||||||
|
|
@ -151,7 +163,7 @@ describe("Image Tool", () => {
|
||||||
const mockSavedFile: SavedFile = {
|
const mockSavedFile: SavedFile = {
|
||||||
path: userPath,
|
path: userPath,
|
||||||
mime_type: "image/png",
|
mime_type: "image/png",
|
||||||
item_label: "Screen 1",
|
item_label: "Safari",
|
||||||
};
|
};
|
||||||
const mockResponse = {
|
const mockResponse = {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -162,7 +174,7 @@ describe("Image Tool", () => {
|
||||||
mockReadImageAsBase64.mockResolvedValue("base64imagedata");
|
mockReadImageAsBase64.mockResolvedValue("base64imagedata");
|
||||||
|
|
||||||
const result = await imageToolHandler(
|
const result = await imageToolHandler(
|
||||||
{ format: "data", path: userPath },
|
{ app_target: "Safari", format: "data", path: userPath },
|
||||||
mockContext,
|
mockContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -644,7 +656,7 @@ describe("Image Tool", () => {
|
||||||
tempDirUsed: MOCK_TEMP_IMAGE_DIR,
|
tempDirUsed: MOCK_TEMP_IMAGE_DIR,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockCliResponse = mockSwiftCli.captureImage("screen", {
|
const mockCliResponse = mockSwiftCli.captureImage("Safari", {
|
||||||
path: MOCK_SAVED_FILE_PATH,
|
path: MOCK_SAVED_FILE_PATH,
|
||||||
format: "png",
|
format: "png",
|
||||||
});
|
});
|
||||||
|
|
@ -652,6 +664,7 @@ describe("Image Tool", () => {
|
||||||
|
|
||||||
const result = await imageToolHandler(
|
const result = await imageToolHandler(
|
||||||
{
|
{
|
||||||
|
app_target: "Safari", // Use app capture to allow format: "data"
|
||||||
question: MOCK_QUESTION,
|
question: MOCK_QUESTION,
|
||||||
format: "data", // Even with format: "data"
|
format: "data", // Even with format: "data"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue