diff --git a/src/domain/adb/adbDeviceAnalysis.test.ts b/src/domain/adb/adbDeviceAnalysis.test.ts index c67a9cc72d..8a90451d43 100644 --- a/src/domain/adb/adbDeviceAnalysis.test.ts +++ b/src/domain/adb/adbDeviceAnalysis.test.ts @@ -99,6 +99,12 @@ it("admits empty string extras and validates typed extra text at domain admissio { type: "int", value: "-2147483648" }, { type: "long", value: "9007199254740993" }, { type: "float", value: "-1.25" }, + { type: "float", value: "1e-3" }, + { type: "float", value: "1E-3" }, + { type: "float", value: ".5" }, + { type: "float", value: "1." }, + { type: "float", value: "0x1.8p1" }, + { type: "float", value: "NaN" }, ] as const; for (const { type, value } of accepted) expect( @@ -112,7 +118,8 @@ it("admits empty string extras and validates typed extra text at domain admissio { type: "boolean", value: "True" }, { type: "int", value: "1.5" }, { type: "long", value: "" }, - { type: "float", value: "1e3" }, + { type: "float", value: "garbage" }, + { type: "float", value: "0x1.8" }, ] as const; for (const { type, value } of rejected) { const result = adbInputSchemas.start_adb_activity.safeParse({ diff --git a/src/domain/adb/adbDeviceAnalysis.ts b/src/domain/adb/adbDeviceAnalysis.ts index 9b068c73b6..52085a8662 100644 --- a/src/domain/adb/adbDeviceAnalysis.ts +++ b/src/domain/adb/adbDeviceAnalysis.ts @@ -59,6 +59,10 @@ const activityExtraStringValueSchema = z .max(2048) .regex(/^[^\x00]*$/u, "Extra values cannot contain NUL"); const activityExtraNonEmptyValueSchema = activityExtraStringValueSchema.min(1); +// Android's `am start --ef` parses this text with Float.valueOf. Keep the +// caller's spelling intact while admitting Java FloatValue forms. +const javaFloatValuePattern = + /^[\x01-\x20]*[+-]?(?:NaN|Infinity|(?:(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?[fFdD]?|0[xX](?:[\da-fA-F]+(?:\.[\da-fA-F]*)?|\.[\da-fA-F]+)[pP][+-]?\d+[fFdD]?))[\x01-\x20]*$/u; const activityExtraSchema = z.discriminatedUnion("type", [ z.strictObject({ key: activityExtraKeySchema, @@ -92,8 +96,13 @@ const activityExtraSchema = z.discriminatedUnion("type", [ key: activityExtraKeySchema, type: z.literal("float").describe("Float extra value type"), value: activityExtraNonEmptyValueSchema - .regex(/^-?\d+(\.\d+)?$/u, "Float extra values must be a number") - .describe("Float extra value as decimal text"), + .regex( + javaFloatValuePattern, + "Float extra value must be valid Java float text", + ) + .describe( + "Float extra text parsed by Android am without changing its spelling", + ), }), ]); diff --git a/tests/boundary/cli/adbCli.test.ts b/tests/boundary/cli/adbCli.test.ts index cd76936cf9..ba41a93c49 100644 --- a/tests/boundary/cli/adbCli.test.ts +++ b/tests/boundary/cli/adbCli.test.ts @@ -244,7 +244,11 @@ it.each([ ["boolean:key:yes", "invalid_request"], ["int:key:1.5", "invalid_request"], ["long:key:1.5", "invalid_request"], - ["float:key:1e3", "invalid_request"], + ["float:key:garbage", "invalid_request"], + ["float:key:1e-3", "capability_unavailable"], + ["float:key:1E-3", "capability_unavailable"], + ["float:key:.5", "capability_unavailable"], + ["float:key:1.", "capability_unavailable"], ["boolean:key:false", "capability_unavailable"], ["int:key:-3", "capability_unavailable"], ["long:key:9007199254740993", "capability_unavailable"], diff --git a/tests/boundary/mcp/adbStartMcp.test.ts b/tests/boundary/mcp/adbStartMcp.test.ts index 8c4665c66e..10670561d9 100644 --- a/tests/boundary/mcp/adbStartMcp.test.ts +++ b/tests/boundary/mcp/adbStartMcp.test.ts @@ -160,7 +160,7 @@ const assertAdvertisedActivityExtras = async ( ["boolean", "yes"], ["int", "1.5"], ["long", ""], - ["float", "1e3"], + ["float", "garbage"], ]) expect( validateInput({ ...input, extras: [{ key: "invalid", type, value }] }), @@ -224,6 +224,7 @@ it extras: [ { key: "mode", type: "string" as const, value: "test" }, { key: "empty", type: "string" as const, value: "" }, + { key: "scale", type: "float" as const, value: "1e-3" }, ], }; const success = await client.callTool({ @@ -312,6 +313,7 @@ it "-n com.example/.Main", "--es mode test", "--es empty ''", + "--ef scale 1e-3", ]), ].join(" "), ),